Compare commits

..

No commits in common. "master" and "v5.2.13" have entirely different histories.

573 changed files with 10004 additions and 11793 deletions

86
.gitattributes vendored
View file

@ -1,78 +1,26 @@
* text=auto eol=lf * text eol=lf
*.[cC][mM][dD] text eol=crlf *.bat text eol=crlf
*.[bB][aA][tT] text eol=crlf *.sh text eol=lf
*.[pP][sS]1 text eol=crlf
*.[sS][hH] text eol=lf
*.patch text eol=lf *.patch text eol=lf
*.java text eol=lf
*.scala text eol=lf
*.groovy text eol=lf
*.gradle text eol=crlf
gradle.properties text eol=crlf
/gradle/wrapper/gradle-wrapper.properties text eol=crlf
*.cfg text eol=lf
*.png binary *.png binary
*.jar binary
*.war binary
*.lzma binary *.lzma binary
*.zip binary *.zip binary
*.gzip binary *.gzip binary
*.dll binary
*.so binary
*.exe binary *.exe binary
*.ico binary
*.eot binary
*.ttf binary
*.woff binary
*.woff2 binary
*.a binary
*.lib binary
*.icns binary
*.jpg binary
*.jpeg binary
*.gif binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.tar binary
*.tar.gz binary
*.7z binary
*.pyc binary
*.gpg binary
*.bin binary
*.gitattributes text *.gitattributes text eol=crlf
.gitignore text *.gitignore text eol=crlf
# Java sources
*.java text diff=java
*.kt text diff=kotlin
*.groovy text diff=java
*.scala text diff=java
*.gradle text diff=java
*.gradle.kts text diff=kotlin
# These files are text and should be normalized (Convert crlf => lf)
*.css text diff=css
*.scss text diff=css
*.sass text
*.df text
*.htm text diff=html
*.html text diff=html
*.js text
*.jsp text
*.jspf text
*.jspx text
*.properties text
*.tld text
*.tag text
*.tagx text
*.xml text
# These files are binary and should be left untouched
# (binary is a macro for -text -diff)
*.class binary
*.dll binary
*.ear binary
*.jar binary
*.so binary
*.war binary
*.jks binary
mvnw text eol=lf
gradlew text eol=lf

View file

@ -6,21 +6,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: recursive submodules: recursive
- name: Cache Gradle - name: Cache Gradle
uses: actions/cache@v4 uses: actions/cache@v1
with: with:
path: ~/.gradle/caches path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v1
with: with:
java-version: 21 java-version: 17
distribution: temurin
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew
@ -28,27 +27,20 @@ jobs:
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5
- name: Create artifacts - name: Create artifacts
run: | run: |
mkdir -p artifacts/modules mkdir -p artifacts/modules
cd LaunchServer/build/libs cd LaunchServer/build/libs
mv proguard proguard-libraries
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar" zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
cd ../../.. cd ../../..
cp ServerWrapper/build/libs/ServerWrapper.jar artifacts/ServerWrapper.jar cp ServerWrapper/build/libs/ServerWrapper.jar artifacts/ServerWrapper.jar
cp ServerWrapper/build/libs/ServerWrapper-inline.jar artifacts/ServerWrapperInline.jar
cp LauncherAuthlib/build/libs/LauncherAuthlib.jar artifacts/LauncherAuthlib.jar || true cp LauncherAuthlib/build/libs/LauncherAuthlib.jar artifacts/LauncherAuthlib.jar || true
cp modules/*_module/build/libs/*.jar artifacts/modules || true cp modules/*_module/build/libs/*.jar artifacts/modules || true
cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true cp modules/*_lmodule/build/libs/*.jar artifacts/modules || true
cp javaargs.txt artifacts/javaargs.txt || true
cp java24args.txt artifacts/java24args.txt || true
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v1
with: with:
name: Launcher name: Launcher
path: artifacts path: artifacts
@ -69,7 +61,7 @@ jobs:
- name: Create release - name: Create release
id: create_release id: create_release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v1
if: startsWith(github.event.ref, 'refs/tags') if: startsWith(github.event.ref, 'refs/tags')
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -24,7 +24,7 @@
**Основные правила:** **Основные правила:**
1. Все коммиты должны быть на английском языке. 1. Все коммиты должны быть на русском языке.
2. Запрещено использовать прошедшее время. 2. Запрещено использовать прошедшее время.
3. Обязательно должен быть использован префикс. 3. Обязательно должен быть использован префикс.
4. В конце не должно быть лишнего знака препинания. 4. В конце не должно быть лишнего знака препинания.
@ -38,10 +38,10 @@
| Префикс | Значение | Пример | | Префикс | Значение | Пример |
| ------- | -------- | ------ | | ------- | -------- | ------ |
| **[FIX]** | Всё, что касается исправления багов | [FIX] Bug with failed authorization | | **[FIX]** | Всё, что касается исправления багов | [FIX] Баг с неудачной авторизацией |
| **[DOCS]** | Всё, что касается документации | [DOCS] Documenting Authorization API | | **[DOCS]** | Всё, что касается документации | [DOCS] Документирование API авторизации |
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA on authorization | | **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA при авторизации |
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Typos in the authorization module | | **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Опечатки в модуле авторизации |
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Switching to EDA in the authorization module | | **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Переход на EDA в модуле авторизации |
| **[TEST]** | Всё, что касается тестирования | [TEST] Coverage of the authorization module with tests | | **[TEST]** | Всё, что касается тестирования | [TEST] Покрытие модуля авторизации тестами |
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Connecting Travis CI | | **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Подключение Travis CI |

View file

@ -1,4 +1,4 @@
def mainClassName = "pro.gravit.launchserver.Main" def mainClassName = "pro.gravit.launchserver.LaunchServerStarter"
def mainAgentName = "pro.gravit.launchserver.StarterAgent" def mainAgentName = "pro.gravit.launchserver.StarterAgent"
evaluationDependsOn(':Launcher') evaluationDependsOn(':Launcher')
@ -13,37 +13,33 @@
maven { maven {
url "https://jitpack.io/" url "https://jitpack.io/"
} }
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
} }
sourceCompatibility = '21' sourceCompatibility = '17'
targetCompatibility = '21' targetCompatibility = '17'
configurations { configurations {
compileOnlyA
bundleOnly bundleOnly
bundle bundle
hikari
pack pack
proguardPack launch4j
bundleOnly.extendsFrom bundle bundleOnly.extendsFrom bundle
api.extendsFrom bundle, pack api.extendsFrom bundle, hikari, pack, launch4j
} }
jar { jar {
dependsOn parent.childProjects.Launcher.tasks.assemble dependsOn parent.childProjects.Launcher.tasks.assemble
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } } from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } }
exclude("module-info.class")
from(parent.childProjects.Launcher.tasks.shadowJar) from(parent.childProjects.Launcher.tasks.shadowJar)
from(parent.childProjects.Launcher.tasks.genRuntimeJS) from(parent.childProjects.Launcher.tasks.genRuntimeJS)
manifest.attributes("Main-Class": mainClassName, manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName, "Premain-Class": mainAgentName,
"Multi-Release": "true", "Multi-Release": "true",
"Automatic-Module-Name": "launchserver" "Can-Redefine-Classes": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
) )
} }
@ -54,102 +50,128 @@
} }
} }
tasks.register('sourcesJar', Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allJava from sourceSets.main.allJava
archiveClassifier.set('sources') archiveClassifier.set('sources')
} }
tasks.register('javadocJar', Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
archiveClassifier.set('javadoc') archiveClassifier.set('javadoc')
} }
tasks.register('cleanjar', Jar) { task cleanjar(type: Jar, dependsOn: jar) {
dependsOn jar
archiveClassifier.set('clean') archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName, manifest.attributes("Main-Class": mainClassName,
"Automatic-Module-Name": "launchserver" "Premain-Class": mainAgentName,
"Can-Redefine-Classes": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
) )
from sourceSets.main.output from sourceSets.main.output
} }
dependencies { dependencies {
pack(project(':LauncherAPI')) { pack project(':LauncherAPI')
exclude group: "com.google.code.gson" bundle group: 'me.tongfei', name: 'progressbar', version: '0.9.2'
} bundle group: 'com.github.Marcono1234', name: 'gson-record-type-adapter-factory', version: 'v0.2.0'
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1'
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
bundle group: 'org.jline', name: 'jline-native', version: rootProject['verJline'] bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline'] bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
bundle group: 'org.jline', name: 'jline-terminal-ffm', version: rootProject['verJline'] bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix'] bundle group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: rootProject['verBcpkix']
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix']
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm'] bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty'] bundle group: 'io.netty', name: 'netty-all', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64'
//bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
//bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
// Netty
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
//
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j'] bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn'] bundle group: 'mysql', name: 'mysql-connector-java', version: rootProject['verMySQLConn']
bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn']
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn'] bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
bundle group: 'com.h2database', name: 'h2', version: rootProject['verH2Conn'] bundle group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
proguardPack group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard']
bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'] bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j']
bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: rootProject['verLog4j'] bundle group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: rootProject['verLog4j']
bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt'] bundle group: 'io.jsonwebtoken', name: 'jjwt-api', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt'] bundle group: 'io.jsonwebtoken', name: 'jjwt-impl', version: rootProject['verJwt']
bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt'] bundle group: 'io.jsonwebtoken', name: 'jjwt-gson', version: rootProject['verJwt']
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson']
annotationProcessor(group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'])
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit'] testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
bundle 'io.micrometer:micrometer-core:1.14.4' hikari 'io.micrometer:micrometer-core:1.8.4'
bundle('com.zaxxer:HikariCP:6.2.1') { hikari('com.zaxxer:HikariCP:5.0.1') {
exclude group: 'javassist' exclude group: 'javassist'
exclude group: 'io.micrometer' exclude group: 'io.micrometer'
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j']) {
exclude group: 'org.apache.ant'
exclude group: 'net.java.abeille'
exclude group: 'foxtrot'
exclude group: 'com.jgoodies'
exclude group: 'org.slf4j'
}
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-win32') { transitive = false }
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-linux64') { transitive = false }
compileOnlyA group: 'com.google.guava', name: 'guava', version: rootProject['verGuavaC']
// Do not update (laggy deps).
compileOnlyA 'log4j:log4j:1.2.17'
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.14.1'
} }
tasks.register('dumpLibs', Copy) { task hikari(type: Copy) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/hikaricp"
from configurations.hikari
}
task launch4j(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/launch4j"
from(configurations.launch4j.collect {
it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it)
})
includeEmptyDirs false
eachFile { FileCopyDetails fcp ->
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
fcp.relativePath.pathString.contains("workdir")) {
def segments = fcp.relativePath.segments
def pathSegments = segments[1..-1] as String[]
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
fcp.mode = 0755
}
}
task dumpLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
dependsOn tasks.hikari, tasks.launch4j
into "$buildDir/libs/libraries" into "$buildDir/libs/libraries"
from configurations.bundleOnly from configurations.bundleOnly
} }
tasks.register('dumpProguard', Copy) { task dumpCompileOnlyLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/proguard" into "$buildDir/libs/launcher-libraries-compile"
from configurations.proguardPack from configurations.compileOnlyA
} }
tasks.register('bundle', Zip) { task bundle(type: Zip) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
archiveFileName = 'LaunchServer.zip' archiveFileName = 'LaunchServer.zip'
destinationDirectory = file("$buildDir") destinationDirectory = file("$buildDir")
from(tasks.dumpLibs.destinationDir) { into 'libraries' } from(tasks.dumpLibs.destinationDir) { into 'libraries' }
from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
from(tasks.jar) from(tasks.jar)
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' } from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
} }
tasks.register('dumpClientLibs', Copy) { task dumpClientLibs(type: Copy) {
dependsOn parent.childProjects.Launcher.tasks.build dependsOn parent.childProjects.Launcher.tasks.build
into "$buildDir/libs/launcher-libraries" into "$buildDir/libs/launcher-libraries"
from parent.childProjects.Launcher.tasks.dumpLibs from parent.childProjects.Launcher.tasks.dumpLibs
} }
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar, tasks.dumpProguard assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
publishing { publishing {
@ -164,7 +186,7 @@
pom { pom {
name = 'GravitLauncher LaunchServer API' name = 'GravitLauncher LaunchServer API'
description = 'GravitLauncher LaunchServer Module API' description = 'GravitLauncher LaunchServer Module API'
url = 'https://gravitlauncher.com' url = 'https://launcher.gravit.pro'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -187,7 +209,7 @@
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://gravitlauncher.com/' url = 'https://launcher.gravit.pro/'
} }
} }
} }

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver; package pro.gravit.launchserver;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launchserver.helper.HttpHelper; import pro.gravit.launchserver.helper.HttpHelper;
import java.io.IOException; import java.io.IOException;
@ -17,11 +17,26 @@ public class HttpRequester {
public HttpRequester() { public HttpRequester() {
} }
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) { public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
return new SimpleErrorHandler<>(clazz); private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if(statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
if(type == Void.class) {
return new HttpHelper.HttpOptional<>(null, null, statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
} }
public <T> SimpleErrorHandler<T> makeEH(Type clazz) { public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz); return new SimpleErrorHandler<>(clazz);
} }
@ -33,7 +48,7 @@ public <T> HttpRequest get(String url, String token) {
.header("Content-Type", "application/json; charset=UTF-8") .header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json") .header("Accept", "application/json")
.timeout(Duration.ofMillis(10000)); .timeout(Duration.ofMillis(10000));
if (token != null) { if(token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token)); requestBuilder.header("Authorization", "Bearer ".concat(token));
} }
return requestBuilder.build(); return requestBuilder.build();
@ -50,7 +65,7 @@ public <T> HttpRequest post(String url, T request, String token) {
.header("Content-Type", "application/json; charset=UTF-8") .header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json") .header("Accept", "application/json")
.timeout(Duration.ofMillis(10000)); .timeout(Duration.ofMillis(10000));
if (token != null) { if(token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token)); requestBuilder.header("Authorization", "Bearer ".concat(token));
} }
return requestBuilder.build(); return requestBuilder.build();
@ -63,30 +78,6 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
return HttpHelper.send(httpClient, request, makeEH(clazz)); return HttpHelper.send(httpClient, request, makeEH(clazz));
} }
public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Type type) throws IOException {
return HttpHelper.send(httpClient, request, makeEH(type));
}
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if (statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
if (type == Void.class) {
return new HttpHelper.HttpOptional<>(null, null, statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public static class SimpleError { public static class SimpleError {
public String error; public String error;
public int code; public int code;

View file

@ -2,43 +2,43 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent; import pro.gravit.launcher.managers.ConfigManager;
import pro.gravit.launcher.base.modules.events.ClosePhase; import pro.gravit.launcher.modules.events.ClosePhase;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider; import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.binary.EXEL4JLauncherBinary;
import pro.gravit.launchserver.binary.EXELauncherBinary; import pro.gravit.launchserver.binary.EXELauncherBinary;
import pro.gravit.launchserver.binary.JARLauncherBinary; import pro.gravit.launchserver.binary.JARLauncherBinary;
import pro.gravit.launchserver.binary.LauncherBinary; import pro.gravit.launchserver.binary.LauncherBinary;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader; import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
import pro.gravit.launchserver.manangers.*; import pro.gravit.launchserver.manangers.*;
import pro.gravit.launchserver.manangers.hook.AuthHookManager; import pro.gravit.launchserver.manangers.hook.AuthHookManager;
import pro.gravit.launchserver.modules.events.*; import pro.gravit.launchserver.modules.events.*;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.SocketCommandServer;
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler; import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse; import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.utils.command.Command; import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.CommandHandler; import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand; import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper; import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.*; import java.nio.file.*;
import java.security.KeyStore; import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
@ -46,6 +46,7 @@
* Not a singletron * Not a singletron
*/ */
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable { public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
public static final Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
/** /**
* Working folder path * Working folder path
*/ */
@ -62,11 +63,9 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
* The path to the folder with compile-only libraries for the launcher * The path to the folder with compile-only libraries for the launcher
*/ */
public final Path launcherLibrariesCompile; public final Path launcherLibrariesCompile;
public final Path launcherPack;
/** /**
* The path to the folder with updates/webroot * The path to the folder with updates/webroot
*/ */
@Deprecated
public final Path updatesDir; public final Path updatesDir;
// Constant paths // Constant paths
@ -77,12 +76,8 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
/** /**
* The path to the folder with profiles * The path to the folder with profiles
*/ */
public final Path profilesDir;
public final Path tmpDir; public final Path tmpDir;
public final Path modulesDir;
public final Path launcherModulesDir;
public final Path librariesDir;
public final Path controlFile;
public final Path proguardDir;
/** /**
* This object contains runtime configuration * This object contains runtime configuration
*/ */
@ -95,6 +90,8 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
* Pipeline for building EXE * Pipeline for building EXE
*/ */
public final LauncherBinary launcherEXEBinary; public final LauncherBinary launcherEXEBinary;
//public static LaunchServer server = null;
public final Class<? extends LauncherBinary> launcherEXEBinaryClass;
// Server config // Server config
public final AuthHookManager authHookManager; public final AuthHookManager authHookManager;
public final LaunchServerModulesManager modulesManager; public final LaunchServerModulesManager modulesManager;
@ -111,21 +108,23 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
// Server // Server
public final CommandHandler commandHandler; public final CommandHandler commandHandler;
public final NettyServerSocketHandler nettyServerSocketHandler; public final NettyServerSocketHandler nettyServerSocketHandler;
public final SocketCommandServer socketCommandServer;
public final ScheduledExecutorService service; public final ScheduledExecutorService service;
public final AtomicBoolean started = new AtomicBoolean(false); public final AtomicBoolean started = new AtomicBoolean(false);
public final LauncherModuleLoader launcherModuleLoader; public final LauncherModuleLoader launcherModuleLoader;
private final Logger logger = LogManager.getLogger(); private final Logger logger = LogManager.getLogger();
public final int shardId;
public LaunchServerConfig config; public LaunchServerConfig config;
// Updates and profiles
private volatile Set<ClientProfile> profilesList;
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException { @SuppressWarnings("deprecation")
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
this.dir = directories.dir; this.dir = directories.dir;
this.tmpDir = directories.tmpDir; this.tmpDir = directories.tmpDir;
this.env = env; this.env = env;
this.config = config; this.config = config;
this.launchServerConfigManager = launchServerConfigManager; this.launchServerConfigManager = launchServerConfigManager;
this.modulesManager = modulesManager; this.modulesManager = modulesManager;
this.profilesDir = directories.profilesDir;
this.updatesDir = directories.updatesDir; this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager; this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler; this.commandHandler = commandHandler;
@ -134,16 +133,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread); this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir; launcherLibraries = directories.launcherLibrariesDir;
launcherLibrariesCompile = directories.launcherLibrariesCompileDir; launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
launcherPack = directories.launcherPackDir;
modulesDir = directories.modules;
launcherModulesDir = directories.launcherModules;
librariesDir = directories.librariesDir;
controlFile = directories.controlFile;
proguardDir = directories.proguardDir;
this.shardId = shardId;
if(!Files.isDirectory(launcherPack)) {
Files.createDirectories(launcherPack);
}
config.setLaunchServer(this); config.setLaunchServer(this);
@ -151,6 +140,9 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
// Print keypair fingerprints // Print keypair fingerprints
// Load class bindings.
launcherEXEBinaryClass = defaultLauncherEXEBinaryClass;
runtime.verify(); runtime.verify();
config.verify(); config.verify();
@ -191,11 +183,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
} }
launcherModuleLoader.init(); launcherModuleLoader.init();
nettyServerSocketHandler = new NettyServerSocketHandler(this); nettyServerSocketHandler = new NettyServerSocketHandler(this);
socketCommandServer = new SocketCommandServer(commandHandler, controlFile);
if(config.sign.checkCertificateExpired) {
checkCertificateExpired();
service.scheduleAtFixedRate(this::checkCertificateExpired, 24, 24, TimeUnit.HOURS);
}
// post init modules // post init modules
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this)); modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
} }
@ -223,14 +210,7 @@ public void reload(ReloadType type) throws Exception {
}); });
logger.debug("Init components successful"); logger.debug("Init components successful");
} }
if(!type.equals(ReloadType.NO_AUTH)) {
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((channel, wsHandler) -> {
Client client = wsHandler.getClient();
if(client.auth != null) {
client.auth = config.getAuthProviderPair(client.auth_id);
}
});
}
} }
@Override @Override
@ -245,8 +225,9 @@ public void invoke(String... args) throws Exception {
} }
switch (args[0]) { switch (args[0]) {
case "full" -> reload(ReloadType.FULL); case "full" -> reload(ReloadType.FULL);
case "no_auth" -> reload(ReloadType.NO_AUTH);
case "no_components" -> reload(ReloadType.NO_COMPONENTS); case "no_components" -> reload(ReloadType.NO_COMPONENTS);
default -> reload(ReloadType.NO_AUTH); default -> reload(ReloadType.FULL);
} }
} }
}; };
@ -272,37 +253,26 @@ public void invoke(String... args) throws Exception {
} }
pair.core.close(); pair.core.close();
pair.core = new RejectAuthCoreProvider(); pair.core = new RejectAuthCoreProvider();
pair.core.init(instance, pair); pair.core.init(instance);
} }
}; };
commands.put("resetauth", resetauth); commands.put("resetauth", resetauth);
return commands; return commands;
} }
public void checkCertificateExpired() { private LauncherBinary binary() {
if(!config.sign.enabled) { if (launcherEXEBinaryClass != null) {
return; try {
return (LauncherBinary) MethodHandles.publicLookup().findConstructor(launcherEXEBinaryClass, MethodType.methodType(void.class, LaunchServer.class)).invoke(this);
} catch (Throwable e) {
logger.error(e);
}
} }
try { try {
KeyStore keyStore = SignHelper.getStore(Paths.get(config.sign.keyStore), config.sign.keyStorePass, config.sign.keyStoreType); Class.forName("net.sf.launch4j.Builder");
Instant date = SignHelper.getCertificateExpired(keyStore, config.sign.keyAlias); if (config.launch4j.enabled) return new EXEL4JLauncherBinary(this);
if(date == null) { } catch (ClassNotFoundException ignored) {
logger.debug("The certificate will expire at unlimited"); logger.warn("Launch4J isn't in classpath.");
} else if(date.minus(Duration.ofDays(30)).isBefore(Instant.now())) {
logger.warn("The certificate will expire at {}", date.toString());
} else {
logger.debug("The certificate will expire at {}", date.toString());
}
} catch (Throwable e) {
logger.error("Can't get certificate expire date", e);
}
}
private LauncherBinary binary() {
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
modulesManager.invokeEvent(event);
if(event.binary != null) {
return event.binary;
} }
return new EXELauncherBinary(this); return new EXELauncherBinary(this);
} }
@ -325,14 +295,12 @@ public void close() throws Exception {
logger.info("LaunchServer stopped"); logger.info("LaunchServer stopped");
} }
@Deprecated
public Set<ClientProfile> getProfiles() { public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles(); return profilesList;
} }
@Deprecated
public void setProfiles(Set<ClientProfile> profilesList) { public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException(); this.profilesList = Collections.unmodifiableSet(profilesList);
} }
public void rebindNettyServerSocket() { public void rebindNettyServerSocket() {
@ -355,17 +323,17 @@ public void run() {
} }
})); }));
CommonHelper.newThread("Command Thread", true, commandHandler).start(); CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
// Sync updates dir // Sync updates dir
CommonHelper.newThread("Profiles and updates sync", true, () -> { CommonHelper.newThread("Profiles and updates sync", true, () -> {
try { try {
if (!IOHelper.isDir(updatesDir))
Files.createDirectory(updatesDir);
updatesManager.readUpdatesDir();
// Sync profiles dir // Sync profiles dir
if (!IOHelper.isDir(profilesDir))
Files.createDirectory(profilesDir);
syncProfilesDir(); syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this)); modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) { } catch (IOException e) {
logger.error("Updates/Profiles not synced", e); logger.error("Updates/Profiles not synced", e);
@ -393,38 +361,40 @@ public void syncLauncherBinaries() throws IOException {
// Syncing launcher EXE binary // Syncing launcher EXE binary
logger.info("Syncing launcher EXE binary file"); logger.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync()) if (!launcherEXEBinary.sync() && config.launch4j.enabled)
logger.warn("Missing launcher EXE binary file"); logger.warn("Missing launcher EXE binary file");
} }
public void syncProfilesDir() throws IOException { public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir"); logger.info("Syncing profiles dir");
config.profileProvider.sync(); List<ClientProfile> newProfies = new LinkedList<>();
if (config.netty.sendProfileUpdatesEvent) { IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false);
sendUpdateProfilesEvent();
}
}
private void sendUpdateProfilesEvent() { // Sort and set new profiles
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) { newProfies.sort(Comparator.comparing(a -> a));
return; profilesList = Set.copyOf(newProfies);
}
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
Client client = handler.getClient();
if (client == null || !client.isAuth) {
return;
}
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
event.requestUUID = RequestEvent.eventUUID;
handler.service.sendObject(ch, event);
});
} }
public void syncUpdatesDir(Collection<String> dirs) throws IOException { public void syncUpdatesDir(Collection<String> dirs) throws IOException {
updatesManager.syncUpdatesDir(dirs); updatesManager.syncUpdatesDir(dirs);
} }
public void restart() {
ProcessBuilder builder = new ProcessBuilder();
if (config.startScript != null) builder.command(Collections.singletonList(config.startScript));
else throw new IllegalArgumentException("Please create start script and link it as startScript in config.");
builder.directory(this.dir.toFile());
builder.inheritIO();
builder.redirectErrorStream(true);
builder.redirectOutput(Redirect.PIPE);
try {
builder.start();
} catch (IOException e) {
logger.error("Restart failed", e);
}
}
public void registerObject(String name, Object object) { public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) { if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object); reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
@ -437,6 +407,11 @@ public void unregisterObject(String name, Object object) {
} }
} }
public void fullyRestart() {
restart();
JVMHelper.RUNTIME.exit(0);
}
public enum ReloadType { public enum ReloadType {
NO_AUTH, NO_AUTH,
@ -461,42 +436,54 @@ public interface LaunchServerConfigManager {
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException; void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
} }
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Collection<ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Collection<ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.add(profile);
return super.visitFile(file, attrs);
}
}
public static class LaunchServerDirectories { public static class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates", public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries", TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", KEY_NAME = ".keys";
KEY_NAME = ".keys", MODULES = "modules", LAUNCHER_MODULES = "launcher-modules",
LIBRARIES = "libraries", CONTROL_FILE = "control-file", PROGUARD_DIR = "proguard-libraries";
public Path updatesDir; public Path updatesDir;
public Path librariesDir; public Path profilesDir;
public Path launcherLibrariesDir; public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir; public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
public Path keyDirectory; public Path keyDirectory;
public Path proguardDir;
public Path dir; public Path dir;
public Path trustStore; public Path trustStore;
public Path tmpDir; public Path tmpDir;
public Path modules;
public Path launcherModules;
public Path controlFile;
public void collect() { public void collect() {
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME); if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
if (profilesDir == null) profilesDir = getPath(PROFILES_NAME);
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME); if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME); if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null) if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME); launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if (launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_NAME);
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME); if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
if (modules == null) modules = getPath(MODULES);
if (launcherModules == null) launcherModules = getPath(LAUNCHER_MODULES);
if (librariesDir == null) librariesDir = getPath(LIBRARIES);
if (controlFile == null) controlFile = getPath(CONTROL_FILE);
if (proguardDir == null) proguardDir = getPath(PROGUARD_DIR);
if (tmpDir == null) if (tmpDir == null)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("launchserver-%s".formatted(SecurityHelper.randomStringToken())); tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));
} }
private Path getPath(String dirName) { private Path getPath(String dirName) {

View file

@ -19,7 +19,6 @@ public class LaunchServerBuilder {
private KeyAgreementManager keyAgreementManager; private KeyAgreementManager keyAgreementManager;
private CertificateManager certificateManager; private CertificateManager certificateManager;
private LaunchServer.LaunchServerConfigManager launchServerConfigManager; private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
private Integer shardId;
public LaunchServerBuilder setConfig(LaunchServerConfig config) { public LaunchServerBuilder setConfig(LaunchServerConfig config) {
this.config = config; this.config = config;
@ -56,11 +55,6 @@ public LaunchServerBuilder setDir(Path dir) {
return this; return this;
} }
public LaunchServerBuilder setShardId(Integer shardId) {
this.shardId = shardId;
return this;
}
public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) { public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) {
this.launchServerConfigManager = launchServerConfigManager; this.launchServerConfigManager = launchServerConfigManager;
return this; return this;
@ -69,15 +63,32 @@ public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServe
public LaunchServer build() throws Exception { public LaunchServer build() throws Exception {
directories.collect(); directories.collect();
if (launchServerConfigManager == null) { if (launchServerConfigManager == null) {
launchServerConfigManager = new NullLaunchServerConfigManager(); launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override
public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException();
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
};
} }
if (keyAgreementManager == null) { if (keyAgreementManager == null) {
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory); keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
} }
if(shardId == null) { return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager);
shardId = Integer.parseInt(System.getProperty("launchserver.shardId", "0"));
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, keyAgreementManager, commandHandler, certificateManager, shardId);
} }
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) { public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
@ -88,26 +99,4 @@ public LaunchServerBuilder setCertificateManager(CertificateManager certificateM
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) { public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
this.keyAgreementManager = keyAgreementManager; this.keyAgreementManager = keyAgreementManager;
} }
private static class NullLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
@Override
public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException();
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() {
throw new UnsupportedOperationException();
}
@Override
public void writeConfig(LaunchServerConfig config) {
throw new UnsupportedOperationException();
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException();
}
}
} }

View file

@ -3,20 +3,17 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.core.LauncherTrustManager; import pro.gravit.launcher.LauncherTrustManager;
import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest; import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component; import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
@ -31,12 +28,14 @@
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.io.*; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.List;
public class LaunchServerStarter { public class LaunchServerStarter {
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned"); public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
@ -44,25 +43,27 @@ public class LaunchServerStarter {
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
JVMHelper.verifySystemProperties(LaunchServer.class, false); JVMHelper.checkStackTrace(LaunchServerStarter.class);
JVMHelper.verifySystemProperties(LaunchServer.class, true);
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log")); //LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
LogHelper.printVersion("LaunchServer"); LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer"); LogHelper.printLicense("LaunchServer");
if (!StarterAgent.isAgentStarted()) {
LogHelper.error("StarterAgent is not started!");
LogHelper.error("You should add to JVM options this option: `-javaagent:LaunchServer.jar`");
}
Path dir = IOHelper.WORKING_DIR; Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile; Path configFile, runtimeConfigFile;
try { try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} catch (ClassNotFoundException | NoClassDefFoundError ex) { } catch (ClassNotFoundException ex) {
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?"); LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
return; return;
} }
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
directories.collect();
CertificateManager certificateManager = new CertificateManager(); CertificateManager certificateManager = new CertificateManager();
try { try {
certificateManager.readTrustStore(directories.trustStore); certificateManager.readTrustStore(dir.resolve("truststore"));
} catch (CertificateException e) { } catch (CertificateException e) {
throw new IOException(e); throw new IOException(e);
} }
@ -83,12 +84,11 @@ public static void main(String[] args) throws Exception {
LaunchServerRuntimeConfig runtimeConfig; LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config; LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION; LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager); LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload(); modulesManager.autoload();
modulesManager.initModules(null); modulesManager.initModules(null);
registerAll(); registerAll();
initGson(modulesManager); initGson(modulesManager);
printExperimentalBranch();
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) { if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
configFile = dir.resolve("LaunchServer.conf"); configFile = dir.resolve("LaunchServer.conf");
} else { } else {
@ -127,7 +127,49 @@ public static void main(String[] args) throws Exception {
} }
} }
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile); LaunchServer.LaunchServerConfigManager launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
try (Writer writer = IOHelper.newWriter(configFile)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
}
};
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
LaunchServer server = new LaunchServerBuilder() LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories) .setDirectories(directories)
.setEnv(env) .setEnv(env)
@ -138,24 +180,7 @@ public static void main(String[] args) throws Exception {
.setLaunchServerConfigManager(launchServerConfigManager) .setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager) .setCertificateManager(certificateManager)
.build(); .build();
List<String> allArgs = List.of(args); if (!prepareMode) {
boolean isPrepareMode = prepareMode || allArgs.contains("--prepare");
boolean isRunCommand = false;
String runCommand = null;
for(var e : allArgs) {
if(e.equals("--run")) {
isRunCommand = true;
continue;
}
if(isRunCommand) {
runCommand = e;
isRunCommand = false;
}
}
if(runCommand != null) {
localCommandHandler.eval(runCommand, false);
}
if (!isPrepareMode) {
server.run(); server.run();
} else { } else {
server.close(); server.close();
@ -167,6 +192,7 @@ public static void initGson(LaunchServerModulesManager modulesManager) {
Launcher.gsonManager.initGson(); Launcher.gsonManager.initGson();
} }
@SuppressWarnings("deprecation")
public static void registerAll() { public static void registerAll() {
AuthCoreProvider.registerProviders(); AuthCoreProvider.registerProviders();
PasswordVerifier.registerProviders(); PasswordVerifier.registerProviders();
@ -178,29 +204,6 @@ public static void registerAll() {
GetAvailabilityAuthRequest.registerProviders(); GetAvailabilityAuthRequest.registerProviders();
OptionalAction.registerProviders(); OptionalAction.registerProviders();
OptionalTrigger.registerProviders(); OptionalTrigger.registerProviders();
MixProvider.registerProviders();
ProfileProvider.registerProviders();
UpdatesProvider.registerProviders();
}
private static void printExperimentalBranch() {
try(Reader reader = IOHelper.newReader(IOHelper.getResourceURL("experimental-build.json"))) {
ExperimentalBuild info = Launcher.gsonManager.configGson.fromJson(reader, ExperimentalBuild.class);
if(info.features == null || info.features.isEmpty()) {
return;
}
logger.warn("This is experimental build. Please do not use this in production");
logger.warn("Experimental features: [{}]", String.join(",", info.features));
for(var e : info.info) {
logger.warn(e);
}
} catch (Throwable e) {
logger.warn("Build information not found");
}
}
record ExperimentalBuild(List<String> features, List<String> info) {
} }
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException { public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
@ -223,7 +226,7 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
address = System.getProperty("launchserver.address", null); address = System.getProperty("launchserver.address", null);
} }
if (address == null) { if (address == null) {
System.out.println("External launchServer address:port (default: localhost:9274): "); System.out.println("LaunchServer address(default: localhost): ");
address = commandHandler.readLine(); address = commandHandler.readLine();
} }
String projectName = System.getenv("PROJECTNAME"); String projectName = System.getenv("PROJECTNAME");
@ -237,29 +240,18 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
newConfig.setProjectName(projectName); newConfig.setProjectName(projectName);
} }
if (address == null || address.isEmpty()) { if (address == null || address.isEmpty()) {
logger.error("Address null. Using localhost:9274"); logger.error("Address null. Using localhost");
address = "localhost:9274"; address = "localhost";
} }
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) { if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
logger.error("ProjectName null. Using MineCraft"); logger.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft"; newConfig.projectName = "MineCraft";
} }
int port = 9274;
if(address.contains(":")) { newConfig.netty.address = "ws://" + address + ":9274/api";
String portString = address.substring(address.indexOf(':')+1); newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
try { newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
port = Integer.parseInt(portString); newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
} catch (NumberFormatException e) {
logger.warn("Unknown port {}, using 9274", portString);
}
} else {
logger.info("Address {} doesn't contains port (you want to use nginx?)", address);
}
newConfig.netty.address = "ws://" + address + "/api";
newConfig.netty.downloadURL = "http://" + address + "/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + "/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + "/Launcher.exe";
newConfig.netty.binds[0].port = port;
// Write LaunchServer config // Write LaunchServer config
logger.info("Writing LaunchServer config file"); logger.info("Writing LaunchServer config file");
@ -267,64 +259,4 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
Launcher.gsonManager.configGson.toJson(newConfig, writer); Launcher.gsonManager.configGson.toJson(newConfig, writer);
} }
} }
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
private final Path configFile;
private final Path runtimeConfigFile;
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
this.configFile = configFile;
this.runtimeConfigFile = runtimeConfigFile;
}
@Override
public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1;
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
return config1;
}
@Override
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
LaunchServerRuntimeConfig config1;
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
return config1;
}
@Override
public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
}
@Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer);
} else {
logger.error("Error writing LaunchServer runtime config file. Gson is null");
}
}
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
}
}
}
} }

View file

@ -1,96 +0,0 @@
package pro.gravit.launchserver;
import pro.gravit.launchserver.holder.LaunchServerControlHolder;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.launch.ClassLoaderControl;
import pro.gravit.utils.launch.LaunchOptions;
import pro.gravit.utils.launch.ModuleLaunch;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Main {
private static final List<String> classpathOnly = List.of("proguard", "progressbar", "kotlin");
private static final String LOG4J_PROPERTY = "log4j2.configurationFile";
private static final String DEBUG_PROPERTY = "launchserver.main.debug";
private static final String LIBRARIES_PROPERTY = "launchserver.dir.libraries";
private static boolean isClasspathOnly(Path path) {
var fileName = path.getFileName().toString();
for(var e : classpathOnly) {
if(fileName.contains(e)) {
return true;
}
}
return false;
}
private static void unpackLog4j() {
String log4jConfigurationFile = System.getProperty(LOG4J_PROPERTY);
if(log4jConfigurationFile == null) {
Path log4jConfigPath = Path.of("log4j2.xml");
if(!Files.exists(log4jConfigPath)) {
try(FileOutputStream output = new FileOutputStream(log4jConfigPath.toFile())) {
try(InputStream input = Main.class.getResourceAsStream("/log4j2.xml")) {
if(input == null) {
return;
}
input.transferTo(output);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.setProperty(LOG4J_PROPERTY, log4jConfigPath.toAbsolutePath().toString());
}
}
public static void main(String[] args) throws Throwable {
unpackLog4j();
ModuleLaunch launch = new ModuleLaunch();
LaunchOptions options = new LaunchOptions();
options.moduleConf = new LaunchOptions.ModuleConf();
Path librariesPath = Path.of(System.getProperty(LIBRARIES_PROPERTY, "libraries"));
List<Path> libraries;
try(Stream<Path> files = Files.walk(librariesPath, FileVisitOption.FOLLOW_LINKS)) {
libraries = new ArrayList<>(files.filter(e -> e.getFileName().toString().endsWith(".jar")).toList());
}
List<Path> classpath = new ArrayList<>();
List<String> modulepath = new ArrayList<>();
for(var l : libraries) {
if(isClasspathOnly(l)) {
classpath.add(l);
} else {
modulepath.add(l.toAbsolutePath().toString());
}
}
classpath.add(IOHelper.getCodeSource(LaunchServerStarter.class));
options.moduleConf.modulePath.addAll(modulepath);
options.moduleConf.modules.add("ALL-MODULE-PATH");
options.moduleConf.enableNativeAccess.add("org.fusesource.jansi");
options.moduleConf.enableNativeAccess.add("io.netty.common");
ClassLoaderControl control = launch.init(classpath, "natives", options);
control.clearLauncherPackages();
control.addLauncherPackage("pro.gravit.utils.launch");
control.addLauncherPackage("pro.gravit.launchserver.holder");
ModuleLayer.Controller controller = (ModuleLayer.Controller) control.getJava9ModuleController();
LaunchServerControlHolder.setControl(control);
LaunchServerControlHolder.setController(controller);
if(Boolean.getBoolean(DEBUG_PROPERTY)) {
for(var e : controller.layer().modules()) {
System.out.printf("Module %s\n", e.getName());
for(var p : e.getPackages()) {
System.out.printf("Package %s\n", p);
}
}
}
launch.launch("pro.gravit.launchserver.LaunchServerStarter", null, Arrays.asList(args));
}
}

View file

@ -1,7 +1,13 @@
package pro.gravit.launchserver; package pro.gravit.launchserver;
import java.io.IOException;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.nio.file.*; import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.*;
import java.util.jar.JarFile;
public final class StarterAgent { public final class StarterAgent {
@ -14,6 +20,47 @@ public static boolean isAgentStarted() {
} }
public static void premain(String agentArgument, Instrumentation inst) { public static void premain(String agentArgument, Instrumentation inst) {
throw new UnsupportedOperationException("Please remove -javaagent option from start.sh"); StarterAgent.inst = inst;
libraries = Paths.get(Optional.ofNullable(agentArgument).map(String::trim).filter(e -> !e.isEmpty()).orElse("libraries"));
isStarted = true;
try {
Files.walkFileTree(libraries, Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new StarterVisitor());
} catch (IOException e) {
e.printStackTrace(System.err);
}
}
private static final class StarterVisitor extends SimpleFileVisitor<Path> {
private static final Set<PosixFilePermission> DPERMS;
static {
Set<PosixFilePermission> perms = new HashSet<>(Arrays.asList(PosixFilePermission.values()));
perms.remove(PosixFilePermission.OTHERS_WRITE);
perms.remove(PosixFilePermission.GROUP_WRITE);
DPERMS = Collections.unmodifiableSet(perms);
}
private final boolean fixLib;
private StarterVisitor() {
Path filef = StarterAgent.libraries.resolve(".libraries_chmoded");
this.fixLib = !Files.exists(filef) && !Boolean.getBoolean("launcher.noLibrariesPosixPermsFix");
if (fixLib) {
try {
Files.deleteIfExists(filef);
Files.createFile(filef);
} catch (Throwable ignored) {
}
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (fixLib && Files.getFileAttributeView(file, PosixFileAttributeView.class) != null)
Files.setPosixFilePermissions(file, DPERMS);
if (file.toFile().getName().endsWith(".jar"))
inst.appendToSystemClassLoaderSearch(new JarFile(file.toFile()));
return super.visitFile(file, attrs);
}
} }
} }

View file

@ -1,17 +1,16 @@
package pro.gravit.launchserver.asm; package pro.gravit.launchserver.asm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.Closeable; import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile; import java.util.jar.JarFile;
/** /**
@ -19,32 +18,14 @@
* чего угодно. Работает через поиск class-файлов в classpath. * чего угодно. Работает через поиск class-файлов в classpath.
*/ */
public class ClassMetadataReader implements Closeable { public class ClassMetadataReader implements Closeable {
private final Logger logger = LogManager.getLogger(ClassMetadataReader.class);
private final List<JarFile> cp; private final List<JarFile> cp;
private final Map<String, Module> moduleClassFinder;
public ClassMetadataReader(List<JarFile> cp) { public ClassMetadataReader(List<JarFile> cp) {
this.cp = cp; this.cp = cp;
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
} }
public ClassMetadataReader() { public ClassMetadataReader() {
this.cp = new ArrayList<>(); this.cp = new ArrayList<>();
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
}
private Map<String, Module> collectModulePackages(ModuleLayer layer) {
var map = new HashMap<String, Module>();
for(var m : layer.modules()) {
for(var p : m.getPackages()) {
map.put(p, m);
}
}
return map;
} }
public List<JarFile> getCp() { public List<JarFile> getCp() {
@ -77,42 +58,7 @@ public byte[] getClassData(String className) throws IOException {
return bytes; return bytes;
} }
} }
if(ClassMetadataReader.class.getModule().isNamed()) { return IOHelper.read(IOHelper.getResourceURL(className + ".class"));
String pkg = getClassPackage(className).replace('/', '.');
var module = moduleClassFinder.get(pkg);
if(module != null) {
var cl = module.getClassLoader();
if(cl == null) {
cl = ClassLoader.getPlatformClassLoader();
}
var stream = cl.getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException("Class "+className + ".class");
}
} else {
throw new FileNotFoundException("Package "+pkg);
}
}
var stream = ClassLoader.getSystemClassLoader().getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException(className + ".class");
}
}
private String getClassPackage(String type) {
int idx = type.lastIndexOf("/");
if(idx <= 0) {
return type;
}
return type.substring(0, idx);
} }
public String getSuperClass(String type) { public String getSuperClass(String type) {
@ -120,7 +66,6 @@ public String getSuperClass(String type) {
try { try {
return getSuperClassASM(type); return getSuperClassASM(type);
} catch (Exception e) { } catch (Exception e) {
logger.warn("getSuperClass: type {} not found ({}: {})", type, e.getClass().getName(), e.getMessage());
return "java/lang/Object"; return "java/lang/Object";
} }
} }
@ -155,7 +100,7 @@ private static class CheckSuperClassVisitor extends ClassVisitor {
String superClassName; String superClassName;
public CheckSuperClassVisitor() { public CheckSuperClassVisitor() {
super(Opcodes.ASM9); super(Opcodes.ASM7);
} }
@Override @Override

View file

@ -4,13 +4,14 @@
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import pro.gravit.launcher.core.LauncherInject; import pro.gravit.launcher.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor; import pro.gravit.launcher.LauncherInjectionConstructor;
import pro.gravit.launchserver.binary.BuildContext; import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.launchserver.binary.tasks.MainBuildTask; import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer { public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
@ -64,9 +65,9 @@ private static void visit(ClassNode classNode, Map<String, Object> values) {
return newClinitMethod; return newClinitMethod;
}); });
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name)) List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
.toList(); .collect(Collectors.toList());
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst() && method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null)); .orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
classNode.fields.forEach(field -> { classNode.fields.forEach(field -> {
// Notice that fields that will be used with this algo should not have default // Notice that fields that will be used with this algo should not have default
@ -91,7 +92,7 @@ public void visit(final String name, final Object value) {
if ("value".equals(name)) { if ("value".equals(name)) {
if (value.getClass() != String.class) if (value.getClass() != String.class)
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid annotation with value class %s".formatted(field.getClass().getName())); String.format("Invalid annotation with value class %s", field.getClass().getName()));
valueName.set(value.toString()); valueName.set(value.toString());
} }
} }
@ -111,7 +112,7 @@ public void visit(final String name, final Object value) {
} }
List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray()) List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray())
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p) .filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTSTATIC).map(p -> (FieldInsnNode) p)
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList(); .filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
InsnList setter = serializeValue(value); InsnList setter = serializeValue(value);
if (putStaticNodes.isEmpty()) { if (putStaticNodes.isEmpty()) {
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc)); setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
@ -125,11 +126,11 @@ public void visit(final String name, final Object value) {
} }
} else { } else {
if (initMethod == null) { if (initMethod == null) {
throw new IllegalArgumentException("Not found init in target: %s".formatted(classNode.name)); throw new IllegalArgumentException(String.format("Not found init in target: %s", classNode.name));
} }
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray()) List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
.filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p) .filter(node -> node instanceof FieldInsnNode && node.getOpcode() == Opcodes.PUTFIELD).map(p -> (FieldInsnNode) p)
.filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).toList(); .filter(node -> node.owner.equals(classNode.name) && node.name.equals(field.name) && node.desc.equals(field.desc)).collect(Collectors.toList());
InsnList setter = serializeValue(value); InsnList setter = serializeValue(value);
if (putFieldNodes.isEmpty()) { if (putFieldNodes.isEmpty()) {
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0)); setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
@ -172,7 +173,8 @@ private static InsnList serializeValue(Object value) {
return ((Serializer) serializerEntry.getValue()).serialize(value); return ((Serializer) serializerEntry.getValue()).serialize(value);
} }
} }
throw new UnsupportedOperationException("Serialization of type %s is not supported".formatted(value.getClass())); throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
value.getClass()));
} }
public static boolean isSerializableValue(Object value) { public static boolean isSerializableValue(Object value) {

View file

@ -149,7 +149,10 @@ public static int opcodeEmulation(AbstractInsnNode e) {
break; break;
case INVOKEVIRTUAL: case INVOKEVIRTUAL:
case INVOKESPECIAL: case INVOKESPECIAL:
case INVOKEINTERFACE, INVOKESTATIC: case INVOKEINTERFACE:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKESTATIC:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc); stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break; break;
case INVOKEDYNAMIC: case INVOKEDYNAMIC:

View file

@ -1,14 +1,12 @@
package pro.gravit.launchserver.auth; package pro.gravit.launchserver.auth;
import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.events.request.AuthRequestEvent;
import java.io.IOException; import java.io.IOException;
import java.io.Serial;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class AuthException extends IOException { public final class AuthException extends IOException {
@Serial
private static final long serialVersionUID = -2586107832847245863L; private static final long serialVersionUID = -2586107832847245863L;
@ -16,10 +14,6 @@ public AuthException(String message) {
super(message); super(message);
} }
public AuthException(String message, Throwable cause) {
super(message, cause);
}
public static AuthException need2FA() { public static AuthException need2FA() {
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE); return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
} }

View file

@ -4,7 +4,8 @@
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.core.MySQLCoreProvider;
import pro.gravit.launchserver.auth.core.PostgresSQLCoreProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import java.io.IOException; import java.io.IOException;
@ -17,12 +18,11 @@ public final class AuthProviderPair {
public boolean isDefault = true; public boolean isDefault = true;
public AuthCoreProvider core; public AuthCoreProvider core;
public TextureProvider textureProvider; public TextureProvider textureProvider;
public Map<String, MixProvider> mixes;
public Map<String, String> links; public Map<String, String> links;
public transient String name; public transient String name;
public transient Set<String> features; public transient Set<String> features;
public String displayName; public String displayName;
public boolean visible = true; private transient boolean warnOAuthShow = false;
public AuthProviderPair() { public AuthProviderPair() {
} }
@ -38,14 +38,21 @@ public static Set<String> getFeatures(Class<?> clazz) {
return list; return list;
} }
public Set<String> getFeatures() { public void internalShowOAuthWarnMessage() {
return features; if(!warnOAuthShow) {
if(!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
}
warnOAuthShow = true;
}
} }
public static void getFeatures(Class<?> clazz, Set<String> list) { public static void getFeatures(Class<?> clazz, Set<String> list) {
Feature[] features = clazz.getAnnotationsByType(Feature.class); Features features = clazz.getAnnotation(Features.class);
for (Feature feature : features) { if (features != null) {
list.add(feature.value()); for (Feature feature : features.value()) {
list.add(feature.value());
}
} }
Class<?> superClass = clazz.getSuperclass(); Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class) { if (superClass != null && superClass != Object.class) {
@ -57,57 +64,39 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
} }
} }
public <T> T isSupport(Class<T> clazz) { public final <T> T isSupport(Class<T> clazz) {
if (core == null) return null; if (core == null) return null;
T result = core.isSupport(clazz); T result = null;
if (result == null && mixes != null) { if (result == null) result = core.isSupport(clazz);
for(var m : mixes.values()) {
result = m.isSupport(clazz);
if(result != null) {
break;
}
}
}
return result; return result;
} }
public void init(LaunchServer srv, String name) { public final void init(LaunchServer srv, String name) {
this.name = name; this.name = name;
if (links != null) link(srv); if (links != null) link(srv);
core.init(srv, this); core.init(srv);
features = new HashSet<>(); features = new HashSet<>();
getFeatures(core.getClass(), features); getFeatures(core.getClass(), features);
if(mixes != null) {
for(var m : mixes.values()) {
m.init(srv, core);
getFeatures(m.getClass(), features);
}
}
} }
public void link(LaunchServer srv) { public final void link(LaunchServer srv) {
links.forEach((k, v) -> { links.forEach((k, v) -> {
AuthProviderPair pair = srv.config.getAuthProviderPair(v); AuthProviderPair pair = srv.config.getAuthProviderPair(v);
if (pair == null) { if (pair == null) {
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v)); throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
} }
if ("core".equals(k)) { if ("core".equals(k)) {
if (pair.core == null) if (pair.core == null)
throw new NullPointerException("Auth %s link failed. %s.core is null".formatted(name, v)); throw new NullPointerException(String.format("Auth %s link failed. %s.core is null", name, v));
core = pair.core; core = pair.core;
} }
}); });
} }
public void close() throws IOException { public final void close() throws IOException {
core.close(); core.close();
if (textureProvider != null) { if (textureProvider != null) {
textureProvider.close(); textureProvider.close();
} }
if(mixes != null) {
for(var m : mixes.values()) {
m.close();
}
}
} }
} }

View file

@ -1,62 +0,0 @@
package pro.gravit.launchserver.auth;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.function.Consumer;
public class HikariSQLSourceConfig implements SQLSourceConfig {
private transient volatile HikariDataSource dataSource;
private String dsClass;
private Properties dsProps;
private String driverClass;
private String jdbcUrl;
private String username;
private String password;
private boolean initializeAtStart;
public void init() {
if(initializeAtStart) {
initializeConnection();
}
}
private void initializeConnection() {
if (dataSource != null) {
return;
}
HikariConfig config = new HikariConfig();
consumeIfNotNull(config::setDataSourceClassName, dsClass);
consumeIfNotNull(config::setDataSourceProperties, dsProps);
consumeIfNotNull(config::setDriverClassName, driverClass);
consumeIfNotNull(config::setJdbcUrl, jdbcUrl);
consumeIfNotNull(config::setUsername, username);
consumeIfNotNull(config::setPassword, password);
this.dataSource = new HikariDataSource(config);
}
@Override
public Connection getConnection() throws SQLException {
if(dataSource == null && !initializeAtStart) {
synchronized (this) {
initializeConnection();
}
}
return dataSource.getConnection();
}
@Override
public void close() {
dataSource.close();
}
private static <T> void consumeIfNotNull(Consumer<T> consumer, T val) {
if (val != null) {
consumer.accept(val);
}
}
}

View file

@ -11,9 +11,7 @@
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES; public final class MySQLSourceConfig implements AutoCloseable {
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
@ -35,7 +33,6 @@ public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
private String password; private String password;
private String database; private String database;
private String timezone; private String timezone;
private long hikariMaxLifetime = MINUTES.toMillis(30);
private boolean useHikari; private boolean useHikari;
// Cache // Cache
@ -111,8 +108,8 @@ public synchronized Connection getConnection() throws SQLException {
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE); hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setConnectionTestQuery("SELECT 1"); hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setConnectionTimeout(1000); hikariConfig.setConnectionTimeout(1000);
hikariConfig.setAutoCommit(true);
hikariConfig.setLeakDetectionThreshold(2000); hikariConfig.setLeakDetectionThreshold(2000);
hikariConfig.setMaxLifetime(hikariMaxLifetime);
// Set HikariCP pool // Set HikariCP pool
// Replace source with hds // Replace source with hds
source = new HikariDataSource(hikariConfig); source = new HikariDataSource(hikariConfig);

View file

@ -10,10 +10,7 @@
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES; public final class PostgreSQLSourceConfig implements AutoCloseable {
import static java.util.concurrent.TimeUnit.SECONDS;
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000"); VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
@ -30,11 +27,9 @@ public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceCon
private String password; private String password;
private String database; private String database;
private long hikariMaxLifetime = MINUTES.toMillis(30); // 30 minutes
// Cache // Cache
private transient DataSource source; private DataSource source;
private transient boolean hikari; private boolean hikari;
@Override @Override
public synchronized void close() { public synchronized void close() {
@ -70,8 +65,7 @@ public synchronized Connection getConnection() throws SQLException {
hikariSource.setPoolName(poolName); hikariSource.setPoolName(poolName);
hikariSource.setMinimumIdle(0); hikariSource.setMinimumIdle(0);
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE); hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariSource.setIdleTimeout(SECONDS.toMillis(TIMEOUT)); hikariSource.setIdleTimeout(TIMEOUT * 1000L);
hikariSource.setMaxLifetime(hikariMaxLifetime);
// Replace source with hds // Replace source with hds
source = hikariSource; source = hikariSource;

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth;
import java.sql.Connection;
import java.sql.SQLException;
public interface SQLSourceConfig {
Connection getConnection() throws SQLException;
void close();
}

View file

@ -1,420 +0,0 @@
package pro.gravit.launchserver.auth.core;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.SECONDS;
public abstract class AbstractSQLCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
public final transient Logger logger = LogManager.getLogger();
public long expireSeconds = HOURS.toSeconds(1);
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String table;
public String permissionsTable;
public String permissionsPermissionColumn;
public String permissionsUUIDColumn;
public String rolesTable;
public String rolesNameColumn;
public String rolesUUIDColumn;
public PasswordVerifier passwordVerifier;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customQueryPermissionsByUUIDSQL;
public String customQueryRolesByUserUUID;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
// Prepared SQL queries
public transient String queryByUUIDSQL;
public transient String queryByUsernameSQL;
public transient String queryByLoginSQL;
public transient String queryPermissionsByUUIDSQL;
public transient String queryRolesByUserUUID;
public transient String updateAuthSQL;
public transient String updateServerIDSQL;
public abstract SQLSourceConfig getSQLConfig();
@Override
public User getUserByUsername(String username) {
try {
return queryUser(queryByUsernameSQL, username);
} catch (Exception e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return queryUser(queryByUUIDSQL, uuid.toString());
} catch (Exception e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
try {
return queryUser(queryByLoginSQL, login);
} catch (Exception e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
try {
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
var user = (SQLUser) getUserByUUID(info.uuid());
if (user == null) {
return null;
}
return createSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
String[] parts = refreshToken.split("\\.");
if (parts.length != 2) {
return null;
}
String username = parts[0];
String token = parts[1];
var user = (SQLUser) getUserByUsername(username);
if (user == null || user.password == null) {
return null;
}
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
if (!token.equals(realToken)) {
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), createSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
SQLUser user = (SQLUser) getUserByLogin(login);
if (user == null) {
throw AuthException.userNotFound();
}
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if (plainPassword == null) {
throw AuthException.wrongPassword();
}
if (!passwordVerifier.check(user.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
SQLUserSession session = createSession(user);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = user.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(user.username, user.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(user, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
}
}
@Override
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
SQLUser sqlUser = (SQLUser) user;
SQLUserSession session = createSession(sqlUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(sqlUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = sqlUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(sqlUser.username, sqlUser.password, server.keyAgreementManager.legacySalt));
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(user, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, SECONDS.toMillis(expireSeconds), session);
}
@Override
public User checkServer(Client client, String username, String serverID) {
SQLUser user = (SQLUser) getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return user;
}
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException {
SQLUser user = (SQLUser) client.getUser();
if (user == null) return false;
return (uuid == null ? user.getUsername().equals(username) : user.getUUID().equals(uuid)) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
if (getSQLConfig() == null) logger.error("SQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = makeUserCols();
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL :
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL :
"SELECT %s FROM %s WHERE %s=? LIMIT 1".formatted(userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL :
"UPDATE %s SET %s=?, %s=NULL WHERE %s=?".formatted(table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL :
"UPDATE %s SET %s=? WHERE %s=?".formatted(table, serverIDColumn, uuidColumn);
if (isEnabledPermissions()) {
if(isEnabledRoles()) {
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
"WITH RECURSIVE req AS (\n" +
"SELECT p."+permissionsPermissionColumn+" FROM "+permissionsTable+" p WHERE p."+permissionsUUIDColumn+" = ?\n" +
"UNION ALL\n" +
"SELECT p."+permissionsPermissionColumn+" FROM "+permissionsTable+" p\n" +
"INNER JOIN "+rolesTable+" r ON p."+permissionsUUIDColumn+" = r."+rolesUUIDColumn+"\n" +
"INNER JOIN req ON r."+rolesUUIDColumn+"=substring(req."+permissionsPermissionColumn+" from 6) or r.name=substring(req."+permissionsPermissionColumn+" from 6)\n" +
") SELECT * FROM req";
queryRolesByUserUUID = customQueryRolesByUserUUID != null ? customQueryRolesByUserUUID : "SELECT r." + rolesNameColumn + " FROM " + rolesTable + " r\n" +
"INNER JOIN " + permissionsTable + " pr ON r." + rolesUUIDColumn + "=substring(pr." + permissionsPermissionColumn + " from 6) or r." + rolesNameColumn + "=substring(pr." + permissionsPermissionColumn + " from 6)\n" +
"WHERE pr." + permissionsUUIDColumn + " = ?";
} else {
queryPermissionsByUUIDSQL = customQueryPermissionsByUUIDSQL != null ? customQueryPermissionsByUUIDSQL :
"SELECT (%s) FROM %s WHERE %s=?".formatted(permissionsPermissionColumn, permissionsTable, permissionsUUIDColumn);
}
}
}
protected String makeUserCols() {
return "%s, %s, %s, %s, %s".formatted(uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
}
protected void updateAuth(User user, String accessToken) throws IOException {
try (Connection c = getSQLConfig().getConnection()) {
SQLUser SQLUser = (SQLUser) user;
SQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
s.executeUpdate();
} catch (SQLException e) {
throw new IOException(e);
}
}
protected boolean updateServerID(User user, String serverID) throws IOException {
try (Connection c = getSQLConfig().getConnection()) {
SQLUser SQLUser = (SQLUser) user;
SQLUser.serverId = serverID;
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
public void close() {
getSQLConfig().close();
}
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn)) : null;
}
public ClientPermissions requestPermissions (String uuid) throws SQLException
{
return new ClientPermissions(isEnabledRoles() ? queryRolesNames(queryRolesByUserUUID,uuid) : new ArrayList<>(),
isEnabledPermissions() ? queryPermissions(queryPermissionsByUUIDSQL,uuid) : new ArrayList<>());
}
private SQLUser queryUser(String sql, String value) throws SQLException {
SQLUser user;
try (Connection c = getSQLConfig().getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
user = constructUser(set);
}
}
if(user != null) {
user.permissions = requestPermissions(user.uuid.toString());
}
return user;
}
private List<String> queryPermissions(String sql, String value) throws SQLException {
try (Connection c = getSQLConfig().getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ResultSet set = s.executeQuery();
List<String> perms = new ArrayList<>();
while (set.next())
perms.add(set.getString(permissionsPermissionColumn));
return perms;
}
}
protected SQLUserSession createSession(SQLUser user) {
return new SQLUserSession(user);
}
public boolean isEnabledPermissions() {
return permissionsPermissionColumn != null;
}
public boolean isEnabledRoles() {
return rolesNameColumn != null;
}
private List<String> queryRolesNames(String sql, String value) throws SQLException {
try (Connection c = getSQLConfig().getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ResultSet set = s.executeQuery();
List<String> perms = new ArrayList<>();
while (set.next())
perms.add(set.getString(rolesNameColumn));
return perms;
}
}
public static class SQLUser implements User {
protected final UUID uuid;
protected final String username;
protected String accessToken;
protected String serverId;
protected final String password;
protected ClientPermissions permissions;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
public String getServerId() {
return serverId;
}
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String toString() {
return "SQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
'}';
}
}
public static class SQLUserSession implements UserSession {
private final SQLUser user;
private final String id;
public SQLUserSession(SQLUser user) {
this.user = user;
this.id = user.username;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return user.getAccessToken();
}
@Override
public long getExpireIn() {
return 0;
}
}
}

View file

@ -3,25 +3,20 @@
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.request.auth.details.AuthPasswordDetails;
import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launcher.base.request.auth.details.AuthPasswordDetails;
import pro.gravit.launcher.base.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable; import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportGetAllUsers; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportGetAllUsers;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRegistration;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.core.openid.OpenIDAuthCoreProvider;
import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -35,7 +30,6 @@
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/* /*
All-In-One provider All-In-One provider
@ -44,8 +38,6 @@ public abstract class AuthCoreProvider implements AutoCloseable, Reconfigurable
public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider"); public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider");
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false; private static boolean registredProviders = false;
protected transient LaunchServer server;
protected transient AuthProviderPair pair;
public static void registerProviders() { public static void registerProviders() {
if (!registredProviders) { if (!registredProviders) {
@ -53,9 +45,7 @@ public static void registerProviders() {
providers.register("mysql", MySQLCoreProvider.class); providers.register("mysql", MySQLCoreProvider.class);
providers.register("postgresql", PostgresSQLCoreProvider.class); providers.register("postgresql", PostgresSQLCoreProvider.class);
providers.register("memory", MemoryAuthCoreProvider.class); providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("merge", MergeAuthCoreProvider.class); providers.register("http", HttpAuthCoreProvider.class);
providers.register("openid", OpenIDAuthCoreProvider.class);
providers.register("sql", SQLCoreProvider.class);
registredProviders = true; registredProviders = true;
} }
} }
@ -82,9 +72,11 @@ public AuthManager.AuthReport authorize(User user, AuthResponse.AuthContext cont
return authorize(user.getUsername(), context, password, minecraftAccess); return authorize(user.getUsername(), context, password, minecraftAccess);
} }
public void init(LaunchServer server, AuthProviderPair pair) { public abstract void init(LaunchServer server);
this.server = server;
this.pair = pair; // Auth Handler methods
protected boolean updateServerID(User user, String serverID) throws IOException {
throw new UnsupportedOperationException();
} }
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) { public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
@ -99,7 +91,7 @@ public Map<String, Command> getCommands() {
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 1); verifyArgs(args, 1);
AuthRequest.AuthPasswordInterface password = null; AuthRequest.AuthPasswordInterface password = null;
if (args.length > 1) { if(args.length > 1) {
if (args[1].startsWith("{")) { if (args[1].startsWith("{")) {
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class); password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
} else { } else {
@ -147,7 +139,7 @@ public void invoke(String... args) throws Exception {
if (instance != null) { if (instance != null) {
map.put("getallusers", new SubCommand("(limit)", "print all users information") { map.put("getallusers", new SubCommand("(limit)", "print all users information") {
@Override @Override
public void invoke(String... args) { public void invoke(String... args) throws Exception {
int max = Integer.MAX_VALUE; int max = Integer.MAX_VALUE;
if (args.length > 0) max = Integer.parseInt(args[0]); if (args.length > 0) max = Integer.parseInt(args[0]);
Iterable<User> users = instance.getAllUsers(); Iterable<User> users = instance.getAllUsers();
@ -189,6 +181,28 @@ public void invoke(String... args) throws Exception {
} }
} }
}); });
map.put("getuserhardware", new SubCommand("[username]", "get hardware by username") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
User user = getUserByUUID(UUID.fromString(args[0]));
if (user == null) {
logger.info("User {} not found", args[0]);
}
UserSupportHardware hardware = instance.fetchUserHardware(user);
if (hardware == null) {
logger.error("Method fetchUserHardware return null");
return;
}
UserHardware userHardware = hardware.getHardware();
if (userHardware == null) {
logger.info("User {} not contains hardware info", args[0]);
} else {
logger.info("UserHardware: {}", userHardware);
logger.info("HardwareInfo(JSON): {}", Launcher.gsonManager.gson.toJson(userHardware.getHardwareInfo()));
}
}
});
map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") { map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
@ -274,78 +288,25 @@ public void invoke(String... args) throws Exception {
}); });
} }
} }
{
var instance = isSupport(AuthSupportSudo.class);
if(instance != null) {
map.put("sudo", new SubCommand("[connectUUID] [username/uuid] [isShadow] (CLIENT/API)", "Authorize connectUUID as another user without password") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
UUID connectUUID = UUID.fromString(args[0]);
String login = args[1];
boolean isShadow = Boolean.parseBoolean(args[2]);
AuthResponse.ConnectTypes type;
if(args.length > 3) {
type = AuthResponse.ConnectTypes.valueOf(args[3]);
} else {
type = AuthResponse.ConnectTypes.CLIENT;
}
User user;
if(login.length() == 36) {
UUID uuid = UUID.fromString(login);
user = getUserByUUID(uuid);
} else {
user = getUserByUsername(login);
}
if(user == null) {
logger.error("User {} not found", login);
return;
}
AtomicBoolean founded = new AtomicBoolean();
server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, fh) -> {
var client = fh.getClient();
if(client == null || !connectUUID.equals(fh.getConnectUUID())) {
return;
}
logger.info("Found connectUUID {} with IP {}", fh.getConnectUUID(), fh.context == null ? "null" : fh.context.ip);
var lock = server.config.netty.performance.disableThreadSafeClientObject ? null : client.writeLock();
if(lock != null) {
lock.lock();
}
try {
var report = instance.sudo(user, isShadow);
User user1 = report.session().getUser();
server.authManager.internalAuth(client, type, pair, user1.getUsername(), user1.getUUID(), user1.getPermissions(), true);
client.sessionObject = report.session();
client.coreObject = report.session().getUser();
PlayerProfile playerProfile = server.authManager.getPlayerProfile(client);
AuthRequestEvent request = new AuthRequestEvent(user1.getPermissions(), playerProfile,
report.minecraftAccessToken(), null, null,
new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()));
request.requestUUID = RequestEvent.eventUUID;
server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, request);
} catch (Throwable e) {
logger.error("Sudo error", e);
} finally {
if(lock != null) {
lock.unlock();
}
founded.set(true);
}
});
if(!founded.get()) {
logger.error("ConnectUUID {} not found", connectUUID);
}
}
});
}
}
return map; return map;
} }
public abstract User checkServer(Client client, String username, String serverID) throws IOException; public User checkServer(Client client, String username, String serverID) throws IOException {
User user = getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return user;
}
return null;
}
public abstract boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException; public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
User user = client.getUser();
if (user == null) return false;
return user.getUsername().equals(username) && user.getAccessToken().equals(accessToken) && updateServerID(user, serverID);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) { public <T> T isSupport(Class<T> clazz) {
@ -354,7 +315,7 @@ public <T> T isSupport(Class<T> clazz) {
} }
@Override @Override
public abstract void close(); public abstract void close() throws IOException;
public static class PasswordVerifyReport { public static class PasswordVerifyReport {
public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1); public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1);

View file

@ -0,0 +1,419 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.profiles.Texture;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
import pro.gravit.launchserver.helper.HttpHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.CommonHelper;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class HttpAuthCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
private transient HttpRequester requester;
public String bearerToken;
public String getUserByUsernameUrl;
public String getUserByLoginUrl;
public String getUserByUUIDUrl;
public String getUserByTokenUrl;
public String getAuthDetails;
public String refreshTokenUrl;
public String authorizeUrl;
public String joinServerUrl;
public String checkServerUrl;
public String updateServerIdUrl;
@Override
public User getUserByUsername(String username) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUsernameUrl, "username", username), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
if(getUserByLoginUrl != null) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
return super.getUserByLogin(login);
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return requester.send(requester.get(CommonHelper.replace(getUserByUUIDUrl, "uuid", uuid.toString()), null), HttpUser.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if(getAuthDetails == null) {
return super.getDetails(client);
}
try {
var result = requester.send(requester.get(getAuthDetails, bearerToken), GetAuthDetailsResponse.class).getOrThrow();
return result.details;
} catch (IOException e) {
logger.error(e);
return super.getDetails(client);
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
if(getUserByTokenUrl == null) {
return null;
}
try {
var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class);
if(!result.isSuccessful()) {
var error = result.error().error;
if(error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) {
throw new OAuthAccessTokenExpired();
}
return null;
}
return result.getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
if(refreshTokenUrl == null) {
return null;
}
try {
return requester.send(requester.post(refreshTokenUrl, new RefreshTokenRequest(refreshToken, context),
null), AuthManager.AuthReport.class).getOrThrow();
} catch (IOException e) {
logger.error(e);
return null;
}
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess),
bearerToken), HttpAuthReport.class);
if(!result.isSuccessful()) {
var error = result.error().error;
if(error != null) {
throw new AuthException(error);
}
}
return result.getOrThrow().toAuthReport();
}
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire,
HttpUserSession session) {
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
var result = requester.send(requester.post(updateServerIdUrl, new UpdateServerIdRequest(user.getUsername(), user.getUUID(), serverID),
null), Void.class);
return result.isSuccessful();
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
return requester.send(requester.post(checkServerUrl, new CheckServerRequest(username, serverID), bearerToken), HttpUser.class).getOrThrow();
}
@Override
public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
var result = requester.send(requester.post(joinServerUrl, new JoinServerRequest(username, accessToken, serverID), bearerToken), Void.class);
return result.isSuccessful();
}
public static class UpdateServerIdRequest {
public String username;
public UUID uuid;
public String serverId;
public UpdateServerIdRequest(String username, UUID uuid, String serverId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
}
}
public static class CheckServerRequest {
public String username;
public String serverId;
public CheckServerRequest(String username, String serverId) {
this.username = username;
this.serverId = serverId;
}
}
public static class GetAuthDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
}
public static class JoinServerRequest {
public String username;
public String accessToken;
public String serverId;
public JoinServerRequest(String username, String accessToken, String serverId) {
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
}
}
@Override
public void init(LaunchServer server) {
requester = new HttpRequester();
if(getUserByUsernameUrl == null) {
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
}
if(getUserByUUIDUrl == null) {
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
}
if(authorizeUrl == null) {
throw new IllegalArgumentException("'authorizeUrl' can't be null");
}
if(checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
}
}
@Override
public void close() throws IOException {
}
public static class AuthorizeRequest {
public String login;
public AuthResponse.AuthContext context;
public AuthRequest.AuthPasswordInterface password;
public boolean minecraftAccess;
public AuthorizeRequest() {
}
public AuthorizeRequest(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) {
this.login = login;
this.context = context;
this.password = password;
this.minecraftAccess = minecraftAccess;
}
}
public static class RefreshTokenRequest {
public String refreshToken;
public AuthResponse.AuthContext context;
public RefreshTokenRequest(String refreshToken, AuthResponse.AuthContext context) {
this.refreshToken = refreshToken;
this.context = context;
}
}
public static class HttpUser implements User, UserSupportTextures, UserSupportProperties {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
@Deprecated
private Texture skin;
@Deprecated
private Texture cloak;
private Map<String, Texture> assets;
private Map<String, String> properties;
public HttpUser() {
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, Map<String, String> properties) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.properties = properties;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Map<String, Texture> assets, Map<String, String> properties) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.assets = assets;
this.properties = properties;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public Texture getSkinTexture() {
if(assets == null) {
return skin;
}
return assets.get("SKIN");
}
@Override
public Texture getCloakTexture() {
if(assets == null) {
return cloak;
}
return assets.get("CAPE");
}
public Map<String, Texture> getAssets() {
if(assets == null) {
Map<String, Texture> map = new HashMap<>();
if(skin != null) {
map.put("SKIN", skin);
}
if(cloak != null) {
map.put("CAPE", cloak);
}
return map;
}
return assets;
}
@Override
public Map<String, String> getProperties() {
if(properties == null) {
return new HashMap<>();
}
return properties;
}
@Override
public String toString() {
return "HttpUser{" +
"username='" + username + '\'' +
", uuid=" + uuid +
", serverId='" + serverId + '\'' +
", accessToken='" + accessToken + '\'' +
", permissions=" + permissions +
", assets=" + getAssets() +
", properties=" + properties +
'}';
}
}
public static class HttpUserSession implements UserSession {
private String id;
private HttpUser user;
private long expireIn;
public HttpUserSession() {
}
public HttpUserSession(String id, HttpUser user, long expireIn) {
this.id = id;
this.user = user;
this.expireIn = expireIn;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
@Override
public String toString() {
return "HttpUserSession{" +
"id='" + id + '\'' +
", user=" + user +
", expireIn=" + expireIn +
'}';
}
}
}

View file

@ -1,11 +1,11 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.details.AuthLoginOnlyDetails; import pro.gravit.launcher.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportSudo;
import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -13,19 +13,15 @@
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class MemoryAuthCoreProvider extends AuthCoreProvider implements AuthSupportSudo { public class MemoryAuthCoreProvider extends AuthCoreProvider {
private transient final List<MemoryUser> memory = new ArrayList<>(16); private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override @Override
public User getUserByUsername(String username) { public User getUserByUsername(String username) {
synchronized (memory) { synchronized (memory) {
for (MemoryUser u : memory) { for(MemoryUser u : memory) {
if (u.username.equals(username)) { if(u.username.equals(username)) {
return u; return u;
} }
} }
@ -43,8 +39,8 @@ public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(
@Override @Override
public User getUserByUUID(UUID uuid) { public User getUserByUUID(UUID uuid) {
synchronized (memory) { synchronized (memory) {
for (MemoryUser u : memory) { for(MemoryUser u : memory) {
if (u.uuid.equals(uuid)) { if(u.uuid.equals(uuid)) {
return u; return u;
} }
} }
@ -53,10 +49,10 @@ public User getUserByUUID(UUID uuid) {
} }
@Override @Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) { public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
synchronized (memory) { synchronized (memory) {
for (MemoryUser u : memory) { for(MemoryUser u : memory) {
if (u.accessToken.equals(accessToken)) { if(u.accessToken.equals(accessToken)) {
return new MemoryUserSession(u); return new MemoryUserSession(u);
} }
} }
@ -71,23 +67,23 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
@Override @Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException { public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if (login == null) { if(login == null) {
throw AuthException.userNotFound(); throw AuthException.userNotFound();
} }
MemoryUser user = null; MemoryUser user = null;
synchronized (memory) { synchronized (memory) {
for (MemoryUser u : memory) { for(MemoryUser u : memory) {
if (u.username.equals(login)) { if(u.username.equals(login)) {
user = u; user = u;
break; break;
} }
} }
if (user == null) { if(user == null) {
user = new MemoryUser(login); user = new MemoryUser(login);
memory.add(user); memory.add(user);
} }
} }
if (!minecraftAccess) { if(!minecraftAccess) {
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user)); return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
} else { } else {
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user)); return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
@ -95,10 +91,17 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
} }
@Override @Override
public User checkServer(Client client, String username, String serverID) { protected boolean updateServerID(User user, String serverID) throws IOException {
MemoryUser memoryUser = (MemoryUser) user;
memoryUser.serverId = serverID;
return true;
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
synchronized (memory) { synchronized (memory) {
for (MemoryUser u : memory) { for(MemoryUser u : memory) {
if (u.username.equals(username)) { if(u.username.equals(username)) {
return u; return u;
} }
} }
@ -109,26 +112,26 @@ public User checkServer(Client client, String username, String serverID) {
} }
@Override @Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) { public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException {
return true; return true;
} }
@Override @Override
public void close() { public void init(LaunchServer server) {
} }
@Override @Override
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException { public void close() throws IOException {
return authorize(user.getUsername(), null, null, true);
} }
public static class MemoryUser implements User { public static class MemoryUser implements User {
private final String username; private String username;
private final UUID uuid; private UUID uuid;
private String serverId; private String serverId;
private final String accessToken; private String accessToken;
private final ClientPermissions permissions; private ClientPermissions permissions;
public MemoryUser(String username) { public MemoryUser(String username) {
this.username = username; this.username = username;
@ -151,6 +154,16 @@ public UUID getUUID() {
return uuid; return uuid;
} }
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override @Override
public ClientPermissions getPermissions() { public ClientPermissions getPermissions() {
return permissions; return permissions;
@ -171,9 +184,9 @@ public int hashCode() {
} }
public static class MemoryUserSession implements UserSession { public static class MemoryUserSession implements UserSession {
private final String id; private String id;
private final MemoryUser user; private MemoryUser user;
private final long expireIn; private long expireIn;
public MemoryUserSession(MemoryUser user) { public MemoryUserSession(MemoryUser user) {
this.id = SecurityHelper.randomStringToken(); this.id = SecurityHelper.randomStringToken();
@ -191,11 +204,6 @@ public User getUser() {
return user; return user;
} }
@Override
public String getMinecraftAccessToken() {
return "IGNORED";
}
@Override @Override
public long getExpireIn() { public long getExpireIn() {
return expireIn; return expireIn;

View file

@ -1,91 +0,0 @@
package pro.gravit.launchserver.auth.core;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MergeAuthCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger(MergeAuthCoreProvider.class);
public List<String> list = new ArrayList<>();
private final transient List<AuthCoreProvider> providers = new ArrayList<>();
@Override
public User getUserByUsername(String username) {
for(var core : providers) {
var result = core.getUserByUsername(username);
if(result != null) {
return result;
}
}
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
for(var core : providers) {
var result = core.getUserByUUID(uuid);
if(result != null) {
return result;
}
}
return null;
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
throw new OAuthAccessTokenExpired(); // Authorization not supported
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
return null;
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
throw new AuthException("Authorization not supported");
}
@Override
public User checkServer(Client client, String username, String serverID) throws IOException {
for(var core : providers) {
var result = core.checkServer(client, username, serverID);
if(result != null) {
return result;
}
}
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return false; // Authorization not supported
}
@Override
public void init(LaunchServer server, AuthProviderPair pair1) {
for(var e : list) {
var pair = server.config.auth.get(e);
if(pair != null) {
providers.add(pair.core);
} else {
logger.warn("Provider {} not found", e);
}
}
}
@Override
public void close() {
// Providers closed automatically
}
}

View file

@ -1,30 +1,59 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest; import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig; import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware { public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
private transient final Logger logger = LogManager.getLogger();
public MySQLSourceConfig mySQLHolder; public MySQLSourceConfig mySQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String hardwareIdColumn; public String hardwareIdColumn;
public String table;
public String tableHWID = "hwids"; public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog"; public String tableHWIDLog = "hwidLog";
public PasswordVerifier passwordVerifier;
public double criticalCompareLevel = 1.0; public double criticalCompareLevel = 1.0;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
private transient String sqlFindHardwareByPublicKey; private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData; private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById; private transient String sqlFindHardwareById;
@ -34,45 +63,186 @@ public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSu
private transient String sqlUpdateHardwareBanned; private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers; private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId; private transient String sqlUsersByHwidId;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override @Override
public SQLSourceConfig getSQLConfig() { public User getUserByUsername(String username) {
return mySQLHolder; try {
return query(queryByUsernameSQL, username);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
} }
@Override @Override
public void init(LaunchServer server, AuthProviderPair pair) { public User getUserByUUID(UUID uuid) {
super.init(server, pair); try {
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth"); return query(queryByUUIDSQL, uuid.toString());
String userInfoCols = makeUserCols(); } catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
try {
return query(queryByLoginSQL, login);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
try {
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
var user = (MySQLUser) getUserByUUID(info.uuid());
if(user == null) {
return null;
}
return new MySQLUserSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
String[] parts = refreshToken.split("\\.");
if(parts.length != 2) {
return null;
}
String username = parts[0];
String token = parts[1];
var user = (MySQLUser) getUserByUsername(username);
if(user == null || user.password == null) {
return null;
}
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
if(!token.equals(realToken)) {
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
MySQLUser mySQLUser = (MySQLUser) getUserByLogin(login);
if(mySQLUser == null) {
throw AuthException.wrongPassword();
}
if(context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if(plainPassword == null) {
throw AuthException.wrongPassword();
}
if(!passwordVerifier.check(mySQLUser.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
MySQLUserSession session = new MySQLUserSession(mySQLUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = mySQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(mySQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
}
}
@Override
public void init(LaunchServer server) {
this.server = server;
if (mySQLHolder == null) logger.error("mySQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = String.format("%s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, hardwareIdColumn);
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
table, serverIDColumn, uuidColumn);
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey"; String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null) if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE `publicKey` = ?".formatted(hardwareInfoCols, tableHWID); sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID);
if (sqlFindHardwareById == null) if (sqlFindHardwareById == null)
sqlFindHardwareById = "SELECT %s FROM %s WHERE `id` = ?".formatted(hardwareInfoCols, tableHWID); sqlFindHardwareById = String.format("SELECT %s FROM %s WHERE `id` = ?", hardwareInfoCols, tableHWID);
if (sqlUsersByHwidId == null) if (sqlUsersByHwidId == null)
sqlUsersByHwidId = "SELECT %s FROM %s WHERE `%s` = ?".formatted(userInfoCols, table, hardwareIdColumn); sqlUsersByHwidId = String.format("SELECT %s FROM %s WHERE `%s` = ?", userInfoCols, table, hardwareIdColumn);
if (sqlFindHardwareByData == null) if (sqlFindHardwareByData == null)
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID); sqlFindHardwareByData = String.format("SELECT %s FROM %s", hardwareInfoCols, tableHWID);
if (sqlCreateHardware == null) if (sqlCreateHardware == null)
sqlCreateHardware = "INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID); sqlCreateHardware = String.format("INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')", tableHWID);
if (sqlCreateHWIDLog == null) if (sqlCreateHWIDLog == null)
sqlCreateHWIDLog = "INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)".formatted(tableHWIDLog); sqlCreateHWIDLog = String.format("INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)", tableHWIDLog);
if (sqlUpdateHardwarePublicKey == null) if (sqlUpdateHardwarePublicKey == null)
sqlUpdateHardwarePublicKey = "UPDATE %s SET `publicKey` = ? WHERE `id` = ?".formatted(tableHWID); sqlUpdateHardwarePublicKey = String.format("UPDATE %s SET `publicKey` = ? WHERE `id` = ?", tableHWID);
sqlUpdateHardwareBanned = "UPDATE %s SET `banned` = ? WHERE `id` = ?".formatted(tableHWID); sqlUpdateHardwareBanned = String.format("UPDATE %s SET `banned` = ? WHERE `id` = ?", tableHWID);
sqlUpdateUsers = "UPDATE %s SET `%s` = ? WHERE `%s` = ?".formatted(table, hardwareIdColumn, uuidColumn); sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
}
protected boolean updateAuth(User user, String accessToken) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
MySQLUser mySQLUser = (MySQLUser) user;
mySQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
} }
@Override @Override
protected String makeUserCols() { protected boolean updateServerID(User user, String serverID) throws IOException {
return super.makeUserCols().concat(", ").concat(hardwareIdColumn); try (Connection c = mySQLHolder.getConnection()) {
MySQLUser mySQLUser = (MySQLUser) user;
mySQLUser.serverId = serverID;
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
} }
@Override @Override
protected MySQLUser constructUser(ResultSet set) throws SQLException { public void close() throws IOException {
mySQLHolder.close();
}
private MySQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn), return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null; set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions(), set.getLong(hardwareIdColumn)) : null;
} }
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException { private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
@ -101,6 +271,19 @@ private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) th
s.executeUpdate(); s.executeUpdate();
} }
private User query(String sql, String value) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override @Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) { public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = mySQLHolder.getConnection()) { try (Connection connection = mySQLHolder.getConnection()) {
@ -188,8 +371,8 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
@Override @Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) { public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
SQLUserSession mySQLUserSession = (SQLUserSession) userSession; MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession;
MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser(); MySQLUser mySQLUser = mySQLUserSession.user;
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware; MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
if (mySQLUser.hwidId == mySQLUserHardware.id) return; if (mySQLUser.hwidId == mySQLUserHardware.id) return;
mySQLUser.hwidId = mySQLUserHardware.id; mySQLUser.hwidId = mySQLUserHardware.id;
@ -261,34 +444,6 @@ public void unbanHardware(UserHardware hardware) {
} }
} }
@Override
protected SQLUserSession createSession(SQLUser user) {
return new MySQLUserSession(user);
}
public class MySQLUserSession extends SQLUserSession implements UserSessionSupportHardware {
private transient MySQLUser mySQLUser;
protected transient MySQLUserHardware hardware;
public MySQLUserSession(SQLUser user) {
super(user);
mySQLUser = (MySQLUser) user;
}
@Override
public String getHardwareId() {
return mySQLUser.hwidId == 0 ? null : String.valueOf(mySQLUser.hwidId);
}
@Override
public UserHardware getHardware() {
if(hardware == null) {
hardware = (MySQLUserHardware) getHardwareInfoById(String.valueOf(mySQLUser.hwidId));
}
return hardware;
}
}
public static class MySQLUserHardware implements UserHardware { public static class MySQLUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo; private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id; private final long id;
@ -333,14 +488,59 @@ public String toString() {
} }
} }
public static class MySQLUser extends SQLUser { public class MySQLUser implements User, UserSupportHardware {
protected UUID uuid;
protected String username;
protected String accessToken;
protected String serverId;
protected String password;
protected ClientPermissions permissions;
protected long hwidId; protected long hwidId;
protected transient MySQLUserHardware hardware;
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) { public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
super(uuid, username, accessToken, serverId, password); this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
this.hwidId = hwidId; this.hwidId = hwidId;
} }
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
MySQLUserHardware result = (MySQLUserHardware) getHardwareInfoById(String.valueOf(hwidId));
hardware = result;
return result;
}
@Override @Override
public String toString() { public String toString() {
return "MySQLUser{" + return "MySQLUser{" +
@ -351,4 +551,29 @@ public String toString() {
'}'; '}';
} }
} }
public static class MySQLUserSession implements UserSession {
private final MySQLUser user;
private final String id;
public MySQLUserSession(MySQLUser user) {
this.user = user;
this.id = user.username;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return 0;
}
}
} }

View file

@ -1,21 +1,297 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig; import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig; import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider { import java.io.IOException;
import java.sql.*;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.UUID;
public class PostgresSQLCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
public PostgreSQLSourceConfig postgresSQLHolder; public PostgreSQLSourceConfig postgresSQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String table;
public PasswordVerifier passwordVerifier;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override @Override
public SQLSourceConfig getSQLConfig() { public User getUserByUsername(String username) {
return postgresSQLHolder; try {
return query(queryByUsernameSQL, username);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
} }
@Override @Override
public void init(LaunchServer server, AuthProviderPair pair) { public User getUserByUUID(UUID uuid) {
super.init(server, pair); try {
logger.warn("Method 'postgresql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth"); return query(queryByUUIDSQL, uuid.toString());
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
try {
return query(queryByLoginSQL, login);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
try {
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
var user = (PostgresSQLUser) getUserByUUID(info.uuid());
if(user == null) {
return null;
}
return new PostgresSQLCoreProvider.MySQLUserSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
String[] parts = refreshToken.split("\\.");
if(parts.length != 2) {
return null;
}
String username = parts[0];
String token = parts[1];
var user = (PostgresSQLUser) getUserByUsername(username);
if(user == null || user.password == null) {
return null;
}
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
if(!token.equals(realToken)) {
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new PostgresSQLCoreProvider.MySQLUserSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) getUserByLogin(login);
if(postgresSQLUser == null) {
throw AuthException.wrongPassword();
}
if(context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if(plainPassword == null) {
throw AuthException.wrongPassword();
}
if(!passwordVerifier.check(postgresSQLUser.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
MySQLUserSession session = new MySQLUserSession(postgresSQLUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = postgresSQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(postgresSQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
}
}
@Override
public void init(LaunchServer server) {
this.server = server;
if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
table, serverIDColumn, uuidColumn);
}
protected boolean updateAuth(User user, String accessToken) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
postgresSQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
postgresSQLUser.serverId = serverID;
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
public void close() throws IOException {
postgresSQLHolder.close();
}
private PostgresSQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new PostgresSQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions()) : null;
}
private User query(String sql, String value) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
public static class PostgresSQLUser implements User {
protected UUID uuid;
protected String username;
protected String accessToken;
protected String serverId;
protected String password;
protected ClientPermissions permissions;
public PostgresSQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String toString() {
return "PostgresSQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
'}';
}
}
public static class MySQLUserSession implements UserSession {
private final PostgresSQLUser user;
private final String id;
public MySQLUserSession(PostgresSQLUser user) {
this.user = user;
this.id = user.username;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return 0;
}
} }
} }

View file

@ -1,9 +1,9 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException; import java.io.IOException;
@ -21,7 +21,7 @@ public User getUserByUUID(UUID uuid) {
} }
@Override @Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) { public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
return null; return null;
} }
@ -41,17 +41,17 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
} }
@Override @Override
public User checkServer(Client client, String username, String serverID) { public void init(LaunchServer server) {
return null;
} }
@Override @Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) { protected boolean updateServerID(User user, String serverID) throws IOException {
return false; return false;
} }
@Override @Override
public void close() { public void close() throws IOException {
} }
} }

View file

@ -1,391 +0,0 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportExtendedCheckServer;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
import pro.gravit.launchserver.socket.Client;
import java.io.IOException;
import java.sql.*;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class SQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware, AuthSupportExtendedCheckServer {
public HikariSQLSourceConfig holder;
@Override
public void close() {
super.close();
holder.close();
}
@Override
public SQLSourceConfig getSQLConfig() {
return holder;
}
public String hardwareIdColumn;
public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog";
public double criticalCompareLevel = 1.0;
private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById;
private transient String sqlCreateHardware;
private transient String sqlCreateHWIDLog;
private transient String sqlUpdateHardwarePublicKey;
private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId;
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
holder.init();
super.init(server, pair);
String userInfoCols = makeUserCols();
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE publicKey = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlFindHardwareById == null)
sqlFindHardwareById = "SELECT %s FROM %s WHERE id = ?".formatted(hardwareInfoCols, tableHWID);
if (sqlUsersByHwidId == null)
sqlUsersByHwidId = "SELECT %s FROM %s WHERE %s = ?".formatted(userInfoCols, table, hardwareIdColumn);
if (sqlFindHardwareByData == null)
sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID);
if (sqlCreateHardware == null)
sqlCreateHardware = "INSERT INTO %s (publickey, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, graphicCard, battery, banned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID);
if (sqlCreateHWIDLog == null)
sqlCreateHWIDLog = "INSERT INTO %s (hwidId, newPublicKey) VALUES (?, ?)".formatted(tableHWIDLog);
if (sqlUpdateHardwarePublicKey == null)
sqlUpdateHardwarePublicKey = "UPDATE %s SET publicKey = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateHardwareBanned = "UPDATE %s SET banned = ? WHERE id = ?".formatted(tableHWID);
sqlUpdateUsers = "UPDATE %s SET %s = ? WHERE %s = ?".formatted(table, hardwareIdColumn, uuidColumn);
}
@Override
protected String makeUserCols() {
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
}
@Override
protected SQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new SQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), set.getLong(hardwareIdColumn)) : null;
}
private SQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException {
HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo();
hardwareInfo.hwDiskId = set.getString("hwDiskId");
hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber");
byte[] displayId = set.getBytes("displayId");
hardwareInfo.displayId = displayId == null ? null : displayId;
hardwareInfo.bitness = set.getInt("bitness");
hardwareInfo.totalMemory = set.getLong("totalMemory");
hardwareInfo.logicalProcessors = set.getInt("logicalProcessors");
hardwareInfo.physicalProcessors = set.getInt("physicalProcessors");
hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq");
hardwareInfo.battery = set.getBoolean("battery");
hardwareInfo.graphicCard = set.getString("graphicCard");
byte[] publicKey = set.getBytes("publicKey");
long id = set.getLong("id");
boolean banned = set.getBoolean("banned");
return new SQLUserHardware(hardwareInfo, publicKey, id, banned);
}
private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) throws SQLException {
PreparedStatement s = connection.prepareStatement(sqlUpdateUsers);
s.setLong(1, hwidId);
s.setString(2, uuid.toString());
s.executeUpdate();
}
@Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey);
s.setBytes(1, publicKey);
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData);
try (ResultSet set = s.executeQuery()) {
while (set.next()) {
SQLUserHardware hw = fetchHardwareInfo(set);
AuthSupportHardware.HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info);
if (result.compareLevel > criticalCompareLevel) {
connection.commit();
return hw;
} else {
connection.commit();
}
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
return null;
}
@Override
public UserHardware getHardwareInfoById(String id) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlFindHardwareById);
s.setLong(1, Long.parseLong(id));
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
connection.commit();
return fetchHardwareInfo(set);
} else {
connection.commit();
return null;
}
}
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) {
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS);
s.setBytes(1, publicKey);
s.setString(2, hardwareInfo.hwDiskId);
s.setString(3, hardwareInfo.baseboardSerialNumber);
s.setBytes(4, hardwareInfo.displayId == null ? null : hardwareInfo.displayId);
s.setInt(5, hardwareInfo.bitness);
s.setLong(6, hardwareInfo.totalMemory);
s.setInt(7, hardwareInfo.logicalProcessors);
s.setInt(8, hardwareInfo.physicalProcessors);
s.setLong(9, hardwareInfo.processorMaxFreq);
s.setString(10, hardwareInfo.graphicCard);
s.setBoolean(11, hardwareInfo.battery);
s.executeUpdate();
try (ResultSet generatedKeys = s.getGeneratedKeys()) {
if (generatedKeys.next()) {
//writeHwidLog(connection, generatedKeys.getLong(1), publicKey);
long id = generatedKeys.getLong(1);
connection.commit();
return new SQLUserHardware(hardwareInfo, publicKey, id, false);
}
}
connection.commit();
return null;
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
AbstractSQLCoreProvider.SQLUserSession SQLUserSession = (AbstractSQLCoreProvider.SQLUserSession) userSession;
SQLUser SQLUser = (SQLUser) SQLUserSession.getUser();
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
if (SQLUser.hwidId == SQLUserHardware.id) return;
SQLUser.hwidId = SQLUserHardware.id;
try (Connection connection = holder.getConnection()) {
setUserHardwareId(connection, SQLUser.getUUID(), SQLUserHardware.id);
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
}
@Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.publicKey = publicKey;
try (Connection connection = holder.getConnection()) {
connection.setAutoCommit(false);
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey);
s.setBytes(1, publicKey);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
connection.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
List<User> users = new LinkedList<>();
try (Connection c = holder.getConnection()) {
c.setAutoCommit(false);
PreparedStatement s = c.prepareStatement(sqlUsersByHwidId);
s.setLong(1, Long.parseLong(hardware.getId()));
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
while (!set.isLast()) {
users.add(constructUser(set));
}
}
c.commit();
} catch (SQLException e) {
logger.error("SQL error", e);
return null;
}
return users;
}
@Override
public void banHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = true;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, true);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL Error", e);
}
}
@Override
public void unbanHardware(UserHardware hardware) {
SQLUserHardware SQLUserHardware = (SQLUserHardware) hardware;
SQLUserHardware.banned = false;
try (Connection connection = holder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, false);
s.setLong(2, SQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
protected AbstractSQLCoreProvider.SQLUserSession createSession(AbstractSQLCoreProvider.SQLUser user) {
return new SQLUserSession(user);
}
@Override
public UserSession extendedCheckServer(Client client, String username, String serverID) {
AbstractSQLCoreProvider.SQLUser user = (AbstractSQLCoreProvider.SQLUser) getUserByUsername(username);
if (user == null) {
return null;
}
if (user.getUsername().equals(username) && user.getServerId().equals(serverID)) {
return createSession(user);
}
return null;
}
public class SQLUserSession extends AbstractSQLCoreProvider.SQLUserSession implements UserSessionSupportHardware {
private transient SQLUser SQLUser;
protected transient SQLUserHardware hardware;
public SQLUserSession(AbstractSQLCoreProvider.SQLUser user) {
super(user);
SQLUser = (SQLUser) user;
}
@Override
public String getHardwareId() {
return SQLUser.hwidId == 0 ? null : String.valueOf(SQLUser.hwidId);
}
@Override
public UserHardware getHardware() {
if(hardware == null) {
hardware = (SQLUserHardware) getHardwareInfoById(String.valueOf(SQLUser.hwidId));
}
return hardware;
}
}
public static class SQLUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public SQLUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "SQLUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public static class SQLUser extends AbstractSQLCoreProvider.SQLUser {
protected long hwidId;
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, long hwidId) {
super(uuid, username, accessToken, serverId, password);
this.hwidId = hwidId;
}
@Override
public String toString() {
return "SQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
", hwidId=" + hwidId +
'}';
}
}
}

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.ClientPermissions;
import java.util.UUID; import java.util.UUID;
@ -9,6 +9,10 @@ public interface User {
UUID getUUID(); UUID getUUID();
String getServerId();
String getAccessToken();
ClientPermissions getPermissions(); ClientPermissions getPermissions();
default boolean isBanned() { default boolean isBanned() {

View file

@ -5,7 +5,5 @@ public interface UserSession {
User getUser(); User getUser();
String getMinecraftAccessToken();
long getExpireIn(); long getExpireIn();
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core.interfaces; package pro.gravit.launchserver.auth.core.interfaces;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest; import pro.gravit.launcher.request.secure.HardwareReportRequest;
public interface UserHardware { public interface UserHardware {
HardwareReportRequest.HardwareInfo getHardwareInfo(); HardwareReportRequest.HardwareInfo getHardwareInfo();

View file

@ -1,22 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.GetAssetUploadUrlRequestEvent;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
import java.util.Set;
@Feature(GetAssetUploadUrlRequestEvent.FEATURE_NAME)
public interface AuthSupportAssetUpload extends AuthSupport {
String getAssetUploadUrl(String name, User user);
default AuthRequestEvent.OAuthRequestEvent getAssetUploadToken(String name, User user) {
return null;
}
default AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
return new AssetUploadInfoRequestEvent(Set.of("SKIN", "CAPE"), AssetUploadInfoRequestEvent.SlimSupportConf.USER);
}
}

View file

@ -4,7 +4,7 @@
import pro.gravit.launchserver.auth.core.UserSession; import pro.gravit.launchserver.auth.core.UserSession;
public interface AuthSupportExit extends AuthSupport { public interface AuthSupportExit extends AuthSupport {
void deleteSession(UserSession session); boolean deleteSession(UserSession session);
void exitUser(User user); boolean exitUser(User user);
} }

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.socket.Client;
import java.io.IOException;
public interface AuthSupportExtendedCheckServer {
UserSession extendedCheckServer(Client client, String username, String serverID);
}

View file

@ -0,0 +1,12 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import java.util.List;
@Feature("sessions")
public interface AuthSupportGetSessionsFromUser extends AuthSupport {
List<UserSession> getSessionsByUser(User user);
}

View file

@ -1,9 +1,10 @@
package pro.gravit.launchserver.auth.core.interfaces.provider; package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest; import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.auth.core.User; import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession; import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.helper.DamerauHelper; import pro.gravit.launchserver.helper.DamerauHelper;
import java.util.Arrays; import java.util.Arrays;
@ -27,6 +28,10 @@ public interface AuthSupportHardware extends AuthSupport {
void unbanHardware(UserHardware hardware); void unbanHardware(UserHardware hardware);
default UserSupportHardware fetchUserHardware(User user) {
return (UserSupportHardware) user;
}
default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) { default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) {
if (hardwareInfo.baseboardSerialNumber != null) if (hardwareInfo.baseboardSerialNumber != null)
hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim(); hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim();

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.core.interfaces.provider; package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.Feature; import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User; import pro.gravit.launchserver.auth.core.User;

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.manangers.AuthManager;
import java.io.IOException;
public interface AuthSupportSudo {
AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException;
}

View file

@ -0,0 +1,20 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportBanInfo;
import java.time.LocalDateTime;
public interface AuthSupportUserBan extends AuthSupport {
UserSupportBanInfo.UserBanInfo banUser(User user, String reason, String moderator, LocalDateTime startTime, LocalDateTime endTime);
default UserSupportBanInfo.UserBanInfo banUser(User user) {
return banUser(user, null, null, LocalDateTime.now(), null);
}
void unbanUser(User user);
default UserSupportBanInfo fetchUserBanInfo(User user) {
return (UserSupportBanInfo) user;
}
}

View file

@ -1,8 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
public interface UserSessionSupportHardware {
String getHardwareId();
UserHardware getHardware();
}

View file

@ -1,13 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import java.security.PrivateKey;
import java.security.PublicKey;
public interface UserSessionSupportKeys {
ClientProfileKeys getClientProfileKeys();
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt,
long refreshedAfter) {
}
}

View file

@ -1,7 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.session;
import java.util.Map;
public interface UserSessionSupportProperties {
Map<String, String> getProperties();
}

View file

@ -0,0 +1,27 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import java.time.LocalDateTime;
public interface UserSupportBanInfo {
UserBanInfo getBanInfo();
interface UserBanInfo {
String getId();
default String getReason() {
return null;
}
default String getModerator() {
return null;
}
default LocalDateTime getStartDate() {
return null;
}
default LocalDateTime getEndDate() {
return null;
}
}
}

View file

@ -0,0 +1,7 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
public interface UserSupportHardware {
UserHardware getHardware();
}

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver.auth.core.interfaces.user; package pro.gravit.launchserver.auth.core.interfaces.user;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.profiles.Texture;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -23,10 +23,10 @@ default Map<String, Texture> getUserAssets() {
var skin = getSkinTexture(); var skin = getSkinTexture();
var cape = getCloakTexture(); var cape = getCloakTexture();
Map<String, Texture> map = new HashMap<>(); Map<String, Texture> map = new HashMap<>();
if (skin != null) { if(skin != null) {
map.put("SKIN", skin); map.put("SKIN", skin);
} }
if (cape != null) { if(cape != null) {
map.put("CAPE", cape); map.put("CAPE", cape);
} }
return map; return map;

View file

@ -1,14 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import com.google.gson.annotations.SerializedName;
public record AccessTokenResponse(@SerializedName("access_token") String accessToken,
@SerializedName("expires_in") Long expiresIn,
@SerializedName("refresh_expires_in") Long refreshExpiresIn,
@SerializedName("refresh_token") String refreshToken,
@SerializedName("token_type") String tokenType,
@SerializedName("id_token") String idToken,
@SerializedName("not-before-policy") Integer notBeforePolicy,
@SerializedName("session_state") String sessionState,
@SerializedName("scope") String scope) {
}

View file

@ -1,178 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class OpenIDAuthCoreProvider extends AuthCoreProvider {
private transient SQLUserStore sqlUserStore;
private transient SQLServerSessionStore sqlSessionStore;
private transient OpenIDAuthenticator openIDAuthenticator;
private OpenIDConfig openIDConfig;
private HikariSQLSourceConfig sqlSourceConfig;
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return openIDAuthenticator.getDetails();
}
@Override
public User getUserByUsername(String username) {
return sqlUserStore.getByUsername(username);
}
@Override
public User getUserByUUID(UUID uuid) {
return sqlUserStore.getUserByUUID(uuid);
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
return openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
}
@Override
public AuthManager.AuthReport refreshAccessToken(String oldRefreshToken, AuthResponse.AuthContext context) {
var tokens = openIDAuthenticator.refreshAccessToken(oldRefreshToken);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new RuntimeException("invalid token", e);
}
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if (password == null) {
throw AuthException.wrongPassword();
}
var authCodePassword = (AuthCodePassword) password;
var tokens = openIDAuthenticator.authorize(authCodePassword);
var accessToken = tokens.accessToken();
var refreshToken = tokens.refreshToken();
var user = openIDAuthenticator.createUserFromToken(accessToken);
long expiresIn = TimeUnit.SECONDS.toMillis(tokens.accessTokenExpiresIn());
sqlUserStore.createOrUpdateUser(user);
UserSession session;
try {
session = openIDAuthenticator.getUserSessionByOAuthAccessToken(accessToken);
} catch (OAuthAccessTokenExpired e) {
throw new AuthException("invalid token", e);
}
if (minecraftAccess) {
var minecraftToken = generateMinecraftToken(user);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftToken, accessToken, refreshToken,
expiresIn, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken,
expiresIn, session);
}
}
private String generateMinecraftToken(User user) {
return Jwts.builder()
.issuer("LaunchServer")
.subject(user.getUUID().toString())
.claim("preferred_username", user.getUsername())
.expiration(Date.from(Instant.now().plus(24, ChronoUnit.HOURS)))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
private User createUserFromMinecraftToken(String accessToken) throws AuthException {
try {
var parser = Jwts.parser()
.requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
var claims = parser.parseSignedClaims(accessToken);
var username = claims.getPayload().get("preferred_username", String.class);
var uuid = UUID.fromString(claims.getPayload().getSubject());
return new UserEntity(username, uuid, new ClientPermissions());
} catch (JwtException e) {
throw new AuthException("Bad minecraft token", e);
}
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
this.sqlSourceConfig.init();
this.sqlUserStore = new SQLUserStore(sqlSourceConfig);
this.sqlUserStore.init();
this.sqlSessionStore = new SQLServerSessionStore(sqlSourceConfig);
this.sqlSessionStore.init();
this.openIDAuthenticator = new OpenIDAuthenticator(openIDConfig);
}
@Override
public User checkServer(Client client, String username, String serverID) {
var savedServerId = sqlSessionStore.getServerIdByUsername(username);
if (!serverID.equals(savedServerId)) {
return null;
}
return sqlUserStore.getByUsername(username);
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
User user;
try {
user = createUserFromMinecraftToken(accessToken);
} catch (AuthException e) {
LogHelper.error(e);
return false;
}
if (!user.getUUID().equals(uuid)) {
return false;
}
sqlUserStore.createOrUpdateUser(user);
return sqlSessionStore.joinServer(user.getUUID(), user.getUsername(), serverID);
}
@Override
public void close() {
sqlSourceConfig.close();
}
}

View file

@ -1,232 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkSet;
import io.jsonwebtoken.security.Jwks;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.base.request.auth.password.AuthCodePassword;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.QueryHelper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.Key;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
public class OpenIDAuthenticator {
private static final HttpClient CLIENT = HttpClient.newBuilder().build();
private final OpenIDConfig openIDConfig;
private final JwtParser jwtParser;
public OpenIDAuthenticator(OpenIDConfig openIDConfig) {
this.openIDConfig = openIDConfig;
var keyLocator = loadKeyLocator(openIDConfig);
this.jwtParser = Jwts.parser()
.keyLocator(keyLocator)
.requireIssuer(openIDConfig.issuer())
.require("azp", openIDConfig.clientId())
.build();
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails() {
var state = UUID.randomUUID().toString();
var uri = QueryBuilder.get(openIDConfig.authorizationEndpoint())
.addQuery("response_type", "code")
.addQuery("client_id", openIDConfig.clientId())
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("scope", openIDConfig.scopes())
.addQuery("state", state)
.toUriString();
return List.of(new AuthWebViewDetails(uri, openIDConfig.redirectUri()));
}
public TokenResponse refreshAccessToken(String oldRefreshToken) {
var postBody = QueryBuilder.post()
.addQuery("grant_type", "refresh_token")
.addQuery("refresh_token", oldRefreshToken)
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
try {
readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new RuntimeException(e);
}
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws AuthCoreProvider.OAuthAccessTokenExpired {
Jws<Claims> token;
try {
token = readAndVerifyToken(accessToken);
} catch (AuthException e) {
throw new AuthCoreProvider.OAuthAccessTokenExpired("Can't read token", e);
}
var user = createUserFromToken(token);
long expiresIn = 0;
var expDate = token.getPayload().getExpiration();
if (expDate != null) {
expiresIn = expDate.toInstant().toEpochMilli();
}
return new OpenIDUserSession(user, accessToken, expiresIn);
}
public TokenResponse authorize(AuthCodePassword authCode) throws IOException {
var uri = URI.create(authCode.uri);
var queries = QueryHelper.splitUriQuery(uri);
String code = CommonHelper.multimapFirstOrNullValue("code", queries);
String error = CommonHelper.multimapFirstOrNullValue("error", queries);
String errorDescription = CommonHelper.multimapFirstOrNullValue("error_description", queries);
if (error != null && !error.isBlank()) {
throw new AuthException("Auth error. Error: %s, description: %s".formatted(error, errorDescription));
}
var postBody = QueryBuilder.post()
.addQuery("grant_type", "authorization_code")
.addQuery("code", code)
.addQuery("redirect_uri", openIDConfig.redirectUri())
.addQuery("client_id", openIDConfig.clientId())
.addQuery("client_secret", openIDConfig.clientSecret())
.toString();
var accessTokenResponse = requestToken(postBody);
var accessToken = accessTokenResponse.accessToken();
var refreshToken = accessTokenResponse.refreshToken();
readAndVerifyToken(accessToken);
var accessTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.expiresIn(), 0L);
var refreshTokenExpiresIn = Objects.requireNonNullElse(accessTokenResponse.refreshExpiresIn(), 0L);
return new TokenResponse(accessToken, accessTokenExpiresIn,
refreshToken, refreshTokenExpiresIn);
}
public User createUserFromToken(String accessToken) throws AuthException {
return createUserFromToken(readAndVerifyToken(accessToken));
}
private Jws<Claims> readAndVerifyToken(String accessToken) throws AuthException {
if (accessToken == null) {
throw new AuthException("Token is null");
}
try {
return jwtParser.parseSignedClaims(accessToken);
} catch (JwtException e) {
throw new AuthException("Bad token", e);
}
}
private User createUserFromToken(Jws<Claims> token) {
var username = token.getPayload().get(openIDConfig.extractorConfig().usernameClaim(), String.class);
var uuidStr = token.getPayload().get(openIDConfig.extractorConfig().uuidClaim(), String.class);
var uuid = UUID.fromString(uuidStr);
return new UserEntity(username, uuid, new ClientPermissions());
}
private AccessTokenResponse requestToken(String postBody) {
var request = HttpRequest.newBuilder()
.uri(openIDConfig.tokenUri())
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(postBody))
.build();
HttpResponse<String> resp;
try {
resp = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return Launcher.gsonManager.gson.fromJson(resp.body(), AccessTokenResponse.class);
}
private static KeyLocator loadKeyLocator(OpenIDConfig openIDConfig) {
var request = HttpRequest.newBuilder(openIDConfig.jwksUri()).GET().build();
HttpResponse<String> response;
try {
response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
var jwks = Jwks.setParser().build().parse(response.body());
return new KeyLocator(jwks);
}
private static class KeyLocator extends LocatorAdapter<Key> {
private final Map<String, Key> keys;
public KeyLocator(JwkSet jwks) {
this.keys = jwks.getKeys().stream().collect(
Collectors.toMap(jwk -> String.valueOf(jwk.get("kid")), Jwk::toKey));
}
@Override
protected Key locate(JweHeader header) {
return super.locate(header);
}
@Override
protected Key locate(JwsHeader header) {
return keys.get(header.getKeyId());
}
@Override
protected Key doLocate(Header header) {
return super.doLocate(header);
}
}
record OpenIDUserSession(User user, String token, long expiresIn) implements UserSession {
@Override
public String getID() {
return user.getUsername();
}
@Override
public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return token;
}
@Override
public long getExpireIn() {
return expiresIn;
}
}
}

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URI;
public record OpenIDConfig(URI tokenUri, String authorizationEndpoint, String clientId, String clientSecret,
String redirectUri, URI jwksUri, String scopes, String issuer,
ClaimExtractorConfig extractorConfig) {
public record ClaimExtractorConfig(String usernameClaim, String uuidClaim) {}
}

View file

@ -1,59 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author Xakep_SDK
*/
public class QueryBuilder {
private final String uri;
private final StringBuilder query = new StringBuilder();
public QueryBuilder(String uri) {
this.uri = uri;
}
public static QueryBuilder get(String uri) {
Objects.requireNonNull(uri, "uri");
if (uri.endsWith("/")) {
uri = uri.substring(0, uri.length() - 1);
}
return new QueryBuilder(uri);
}
public static QueryBuilder post() {
return new QueryBuilder(null);
}
public QueryBuilder addQuery(String key, String value) {
if (!query.isEmpty()) {
query.append('&');
}
query.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
return this;
}
public String toUriString() {
if (uri != null) {
if (query. isEmpty()) {
return uri;
}
return uri + '?' + query;
}
return toQueryString();
}
public String toQueryString() {
return query.toString();
}
@Override
public String toString() {
return toUriString();
}
}

View file

@ -1,97 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLServerSessionStore implements ServerSessionStore {
private static final String CREATE_TABLE = """
create table if not exists `gravit_server_session` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
server_id varchar(41),
primary key (id),
unique (uuid),
unique (username)
);
""";
private static final String DELETE_SERVER_ID = """
delete from `gravit_server_session` where uuid = ?
""";
private static final String INSERT_SERVER_ID = """
insert into `gravit_server_session` (uuid, username, server_id) values (?, ?, ?)
""";
private static final String SELECT_SERVER_ID_BY_USERNAME = """
select server_id from `gravit_server_session` where username = ?
""";
private final SQLSourceConfig sqlSourceConfig;
public SQLServerSessionStore(SQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public boolean joinServer(UUID uuid, String username, String serverId) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteServerIdStmt = connection.prepareStatement(DELETE_SERVER_ID);
var insertServerIdStmt = connection.prepareStatement(INSERT_SERVER_ID)) {
deleteServerIdStmt.setString(1, uuid.toString());
deleteServerIdStmt.execute();
insertServerIdStmt.setString(1, uuid.toString());
insertServerIdStmt.setString(2, username);
insertServerIdStmt.setString(3, serverId);
insertServerIdStmt.execute();
connection.commit();
return true;
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Can't join server. Username: %s".formatted(username));
LogHelper.error(e);
}
return false;
}
@Override
public String getServerIdByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectServerId = connection.prepareStatement(SELECT_SERVER_ID_BY_USERNAME)) {
selectServerId.setString(1, username);
try (var rs = selectServerId.executeQuery()) {
if (!rs.next()) {
return null;
}
return rs.getString("server_id");
}
} catch (SQLException e) {
LogHelper.debug("Can't find server id by username. Username: %s".formatted(username));
LogHelper.error(e);
}
return null;
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createTableStmt = connection.prepareStatement(CREATE_TABLE)) {
createTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,124 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.HikariSQLSourceConfig;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.utils.helper.LogHelper;
import java.sql.SQLException;
import java.util.UUID;
public class SQLUserStore implements UserStore {
private static final String CREATE_USER_TABLE = """
create table if not exists `gravit_user` (
id int auto_increment,
uuid varchar(36),
username varchar(255),
primary key (id),
unique (uuid),
unique (username)
)
""";
private static final String INSERT_USER = """
insert into `gravit_user` (uuid, username) values (?, ?)
""";
private static final String DELETE_USER_BY_NAME = """
delete `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_NAME = """
select uuid, username from `gravit_user` where username = ?
""";
private static final String SELECT_USER_BY_UUID = """
select uuid, username from `gravit_user` where uuid = ?
""";
private final HikariSQLSourceConfig sqlSourceConfig;
public SQLUserStore(HikariSQLSourceConfig sqlSourceConfig) {
this.sqlSourceConfig = sqlSourceConfig;
}
@Override
public User getByUsername(String username) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_NAME)) {
selectUserStmt.setString(1, username);
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, username: %s".formatted(username));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
try (var connection = sqlSourceConfig.getConnection();
var selectUserStmt = connection.prepareStatement(SELECT_USER_BY_UUID)) {
selectUserStmt.setString(1, uuid.toString());
try (var rs = selectUserStmt.executeQuery()) {
if (!rs.next()) {
LogHelper.debug("User not found, UUID: %s".formatted(uuid));
return null;
}
return new UserEntity(rs.getString("username"),
UUID.fromString(rs.getString("uuid")),
new ClientPermissions());
}
} catch (SQLException e) {
LogHelper.error(e);
}
return null;
}
@Override
public void createOrUpdateUser(User user) {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var deleteUserStmt = connection.prepareStatement(DELETE_USER_BY_NAME);
var insertUserStmt = connection.prepareStatement(INSERT_USER)) {
deleteUserStmt.setString(1, user.getUsername());
deleteUserStmt.execute();
insertUserStmt.setString(1, user.getUUID().toString());
insertUserStmt.setString(2, user.getUsername());
insertUserStmt.execute();
connection.commit();
LogHelper.debug("User saved. UUID: %s, username: %s".formatted(user.getUUID(), user.getUsername()));
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
LogHelper.debug("Failed to save user");
LogHelper.error(e);
throw new RuntimeException("Failed to save user", e);
}
}
public void init() {
try (var connection = sqlSourceConfig.getConnection()) {
connection.setAutoCommit(false);
var savepoint = connection.setSavepoint();
try (var createUserTableStmt = connection.prepareStatement(CREATE_USER_TABLE)) {
createUserTableStmt.execute();
connection.commit();
} catch (Exception e) {
connection.rollback(savepoint);
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,8 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import java.util.UUID;
public interface ServerSessionStore {
boolean joinServer(UUID uuid, String username, String serverId);
String getServerIdByUsername(String username);
}

View file

@ -1,5 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
public record TokenResponse(String accessToken, long accessTokenExpiresIn,
String refreshToken, long refreshTokenExpiresIn) {
}

View file

@ -1,23 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launcher.base.ClientPermissions;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
record UserEntity(String username, UUID uuid, ClientPermissions permissions) implements User {
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
}

View file

@ -1,13 +0,0 @@
package pro.gravit.launchserver.auth.core.openid;
import pro.gravit.launchserver.auth.core.User;
import java.util.UUID;
public interface UserStore {
User getByUsername(String username);
User getUserByUUID(UUID uuid);
void createOrUpdateUser(User user);
}

View file

@ -1,31 +0,0 @@
package pro.gravit.launchserver.auth.mix;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.utils.ProviderMap;
public abstract class MixProvider implements AutoCloseable{
public static final ProviderMap<MixProvider> providers = new ProviderMap<>("MixProvider");
private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false;
public static void registerProviders() {
if (!registredProviders) {
providers.register("uploadAsset", UploadAssetMixProvider.class);
registredProviders = true;
}
}
public abstract void init(LaunchServer server, AuthCoreProvider core);
@SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) {
if (clazz.isAssignableFrom(getClass())) return (T) this;
return null;
}
@Override
public abstract void close();
}

View file

@ -1,34 +0,0 @@
package pro.gravit.launchserver.auth.mix;
import pro.gravit.launcher.base.events.request.AssetUploadInfoRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportAssetUpload;
import java.util.Map;
public class UploadAssetMixProvider extends MixProvider implements AuthSupportAssetUpload {
public Map<String, String> urls;
public AssetUploadInfoRequestEvent.SlimSupportConf slimSupportConf;
@Override
public String getAssetUploadUrl(String name, User user) {
return urls.get(name);
}
@Override
public AssetUploadInfoRequestEvent getAssetUploadInfo(User user) {
return new AssetUploadInfoRequestEvent(urls.keySet(), slimSupportConf);
}
@Override
public void init(LaunchServer server, AuthCoreProvider core) {
}
@Override
public void close() {
}
}

View file

@ -1,18 +0,0 @@
package pro.gravit.launchserver.auth.password;
import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
import pro.gravit.utils.helper.SecurityHelper;
public class BCryptPasswordVerifier extends PasswordVerifier {
public int cost = 10;
@Override
public boolean check(String encryptedPassword, String password) {
return OpenBSDBCrypt.checkPassword(encryptedPassword, password.toCharArray());
}
@Override
public String encrypt(String password) {
return OpenBSDBCrypt.generate(password.toCharArray(), SecurityHelper.randomBytes(16), cost);
}
}

View file

@ -2,7 +2,7 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -14,11 +14,34 @@
import java.time.Duration; import java.time.Duration;
public class JsonPasswordVerifier extends PasswordVerifier { public class JsonPasswordVerifier extends PasswordVerifier {
private static final Logger logger = LogManager.getLogger(); private static transient final Logger logger = LogManager.getLogger();
private transient final HttpClient client = HttpClient.newBuilder().build(); private transient final HttpClient client = HttpClient.newBuilder().build();
public String url; public String url;
public String bearerToken; public String bearerToken;
@Override
public boolean check(String encryptedPassword, String password) {
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
if (response != null) {
return response.success;
}
return false;
}
public static class JsonPasswordRequest {
public String encryptedPassword;
public String password;
public JsonPasswordRequest(String encryptedPassword, String password) {
this.encryptedPassword = encryptedPassword;
this.password = password;
}
}
public static class JsonPasswordResponse {
public boolean success;
}
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) { public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
HttpRequest.BodyPublisher publisher; HttpRequest.BodyPublisher publisher;
if (request != null) { if (request != null) {
@ -55,27 +78,4 @@ public static <T, R> R jsonRequest(T request, String url, String bearerToken, Cl
return null; return null;
} }
} }
@Override
public boolean check(String encryptedPassword, String password) {
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
if (response != null) {
return response.success;
}
return false;
}
public static class JsonPasswordRequest {
public String encryptedPassword;
public String password;
public JsonPasswordRequest(String encryptedPassword, String password) {
this.encryptedPassword = encryptedPassword;
this.password = password;
}
}
public static class JsonPasswordResponse {
public boolean success;
}
} }

View file

@ -12,7 +12,6 @@ public static void registerProviders() {
providers.register("digest", DigestPasswordVerifier.class); providers.register("digest", DigestPasswordVerifier.class);
providers.register("doubleDigest", DoubleDigestPasswordVerifier.class); providers.register("doubleDigest", DoubleDigestPasswordVerifier.class);
providers.register("json", JsonPasswordVerifier.class); providers.register("json", JsonPasswordVerifier.class);
providers.register("bcrypt", BCryptPasswordVerifier.class);
providers.register("accept", AcceptPasswordVerifier.class); providers.register("accept", AcceptPasswordVerifier.class);
providers.register("reject", RejectPasswordVerifier.class); providers.register("reject", RejectPasswordVerifier.class);
registeredProviders = true; registeredProviders = true;

View file

@ -1,108 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
public class LocalProfileProvider extends ProfileProvider {
public String profilesDir = "profiles";
private transient volatile Map<Path, ClientProfile> profilesMap;
private transient volatile Set<ClientProfile> profilesList; // Cache
@Override
public void sync() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
Map<Path, ClientProfile> newProfiles = new HashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
Set<ClientProfile> newProfilesList = new HashSet<>(newProfiles.values());
profilesMap = newProfiles;
profilesList = newProfilesList;
}
@Override
public Set<ClientProfile> getProfiles() {
return profilesList;
}
@Override
public void addProfile(ClientProfile profile) throws IOException {
Path profilesDirPath = Path.of(profilesDir);
ClientProfile oldProfile;
Path target = null;
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
target = e.getKey();
}
}
if(target == null) {
target = profilesDirPath.resolve(profile.getTitle()+".json");
oldProfile = profilesMap.get(target);
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
throw new FileAlreadyExistsException(target.toString());
}
}
try (BufferedWriter writer = IOHelper.newWriter(target)) {
Launcher.gsonManager.configGson.toJson(profile, writer);
}
addProfile(target, profile);
}
@Override
public void deleteProfile(ClientProfile profile) throws IOException {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
Files.deleteIfExists(e.getKey());
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
}
private void addProfile(Path path, ClientProfile profile) {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
profilesMap.put(path, profile);
profilesList.add(profile);
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<Path, ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<Path, ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.put(file.toAbsolutePath(), profile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -1,76 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class ProfileProvider {
public static final ProviderMap<ProfileProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfileProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract void sync() throws IOException;
public abstract Set<ClientProfile> getProfiles();
public abstract void addProfile(ClientProfile profile) throws IOException;
public abstract void deleteProfile(ClientProfile profile) throws IOException;
public void close() {
}
public ClientProfile getProfile(UUID uuid) {
for(var e : getProfiles()) {
if(e.getUUID().equals(uuid)) {
return e;
}
}
return null;
}
public ClientProfile getProfile(String title) {
for(var e : getProfiles()) {
if(e.getTitle().equals(title)) {
return e;
}
}
return null;
}
public List<ClientProfile> getProfiles(Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
profileList = new ArrayList<>(4);
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
} else {
profileList = List.copyOf(serverProfiles);
}
return profileList;
}
}

View file

@ -4,9 +4,10 @@
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent; import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.HardwareReportRequestEvent; import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent; import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
@ -15,20 +16,28 @@
import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler; import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler;
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler; import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse; import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse; import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.SECONDS;
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler { public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public boolean enableHardwareFeature; public boolean enableHardwareFeature;
private transient LaunchServer server; private transient LaunchServer server;
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
}
@Override
public void checkLaunchServerLicense() {
}
@Override @Override
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) { public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
return event; return event;
@ -42,17 +51,13 @@ public boolean allowGetSecureLevelInfo(Client client) {
@Override @Override
public void onHardwareReport(HardwareReportResponse response, Client client) { public void onHardwareReport(HardwareReportResponse response, Client client) {
if (!enableHardwareFeature) { if (!enableHardwareFeature) {
response.sendResult(new HardwareReportRequestEvent()); response.sendResult(new HardwareReportRequestEvent(null));
return; return;
} }
if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) { if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) {
response.sendError("Access denied"); response.sendError("Access denied");
return; return;
} }
if(client.trustLevel.hardwareInfo != null) {
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, client.trustLevel.hardwareInfo), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
return;
}
logger.debug("HardwareInfo received"); logger.debug("HardwareInfo received");
{ {
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class); var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
@ -67,11 +72,13 @@ public void onHardwareReport(HardwareReportResponse response, Client client) {
if (hardware.isBanned()) { if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned"); throw new SecurityException("Your hardware banned");
} }
client.trustLevel.hardwareInfo = hardware; client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire))); response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware)));
return;
} else { } else {
logger.error("AuthCoreProvider not supported hardware"); logger.error("AuthCoreProvider not supported hardware");
response.sendError("AuthCoreProvider not supported hardware"); response.sendError("AuthCoreProvider not supported hardware");
return;
} }
} }
} }
@ -83,22 +90,22 @@ public VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
if (authSupportHardware != null) { if (authSupportHardware != null) {
UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey); UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey);
if (hardware == null) //HWID not found? if (hardware == null) //HWID not found?
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)); return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
if (hardware.isBanned()) { if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned"); throw new SecurityException("Your hardware banned");
} }
client.trustLevel.hardwareInfo = hardware; client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware); authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)); return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), createHardwareToken(client.username, hardware));
} else { } else {
logger.warn("AuthCoreProvider not supported hardware. HardwareInfo not checked!"); logger.warn("AuthCoreProvider not supported hardware. HardwareInfo not checked!");
} }
} }
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)); return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey));
} }
@Override @Override
public boolean onJoinServer(String serverID, String username, UUID uuid, Client client) { public boolean onJoinServer(String serverID, String username, Client client) {
return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null); return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null);
} }
@ -107,11 +114,15 @@ public void init(LaunchServer server) {
this.server = server; this.server = server;
} }
@Override
public void close() {
}
public String createHardwareToken(String username, UserHardware hardware) { public String createHardwareToken(String username, UserHardware hardware) {
return Jwts.builder() return Jwts.builder()
.setIssuer("LaunchServer") .setIssuer("LaunchServer")
.setSubject(username) .setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire))) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
.claim("hardware", hardware.getId()) .claim("hardware", hardware.getId())
.signWith(server.keyAgreementManager.ecdsaPrivateKey) .signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact(); .compact();
@ -121,20 +132,22 @@ public String createPublicKeyToken(String username, byte[] publicKey) {
return Jwts.builder() return Jwts.builder()
.setIssuer("LaunchServer") .setIssuer("LaunchServer")
.setSubject(username) .setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire))) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey)) .claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
.signWith(server.keyAgreementManager.ecdsaPrivateKey) .signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact(); .compact();
} }
public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider { public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final LaunchServer server;
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser; private final JwtParser parser;
public HardwareInfoTokenVerifier(LaunchServer server) { public HardwareInfoTokenVerifier(LaunchServer server) {
this.parser = Jwts.parser() this.server = server;
this.parser = Jwts.parserBuilder()
.requireIssuer("LaunchServer") .requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey) .setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
.build(); .build();
} }
@ -144,12 +157,12 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
var parse = parser.parseClaimsJws(extendedToken); var parse = parser.parseClaimsJws(extendedToken);
String hardwareInfoId = parse.getBody().get("hardware", String.class); String hardwareInfoId = parse.getBody().get("hardware", String.class);
if (hardwareInfoId == null) return false; if (hardwareInfoId == null) return false;
if (client.auth == null) return false; if(client.auth == null) return false;
var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class); var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class);
if (hardwareSupport == null) return false; if(hardwareSupport == null) return false;
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId); UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel(); if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
client.trustLevel.hardwareInfo = hardware; client.trustLevel.hardwareInfo = hardware.getHardwareInfo();
return true; return true;
} catch (Throwable e) { } catch (Throwable e) {
logger.error("Hardware JWT error", e); logger.error("Hardware JWT error", e);
@ -160,13 +173,15 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
} }
public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider { public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final LaunchServer server;
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser; private final JwtParser parser;
public PublicKeyTokenVerifier(LaunchServer server) { public PublicKeyTokenVerifier(LaunchServer server) {
this.parser = Jwts.parser() this.server = server;
this.parser = Jwts.parserBuilder()
.requireIssuer("LaunchServer") .requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey) .setSigningKey(server.keyAgreementManager.ecdsaPublicKey)
.build(); .build();
} }

View file

@ -1,6 +1,5 @@
package pro.gravit.launchserver.auth.protect; package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
public class NoProtectHandler extends ProtectHandler { public class NoProtectHandler extends ProtectHandler {
@ -11,7 +10,7 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
} }
@Override @Override
public boolean allowJoinServer(Client client) { public void checkLaunchServerLicense() {
return true; // None
} }
} }

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver.auth.protect; package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
@ -20,9 +19,8 @@ public static void registerHandlers() {
} }
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context); public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
public boolean allowJoinServer(Client client) {
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT; public abstract void checkLaunchServerLicense(); //Выдает SecurityException при ошибке проверки лицензии
}
public void init(LaunchServer server) { public void init(LaunchServer server) {

View file

@ -2,7 +2,7 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
@ -20,21 +20,26 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign; return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
} }
@Override
public void checkLaunchServerLicense() {
}
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
if (profileWhitelist != null && !profileWhitelist.isEmpty()) { if(profileWhitelist != null && profileWhitelist.size() > 0) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'"); logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
} }
} }
@Override @Override
public boolean canGetProfile(ClientProfile profile, Client client) { public boolean canGetProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client); return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.show", profile, client);
} }
@Override @Override
public boolean canChangeProfile(ClientProfile profile, Client client) { public boolean canChangeProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client); return !profile.isLimited() || isWhitelisted("launchserver.profile.%s.enter", profile, client);
} }
@Override @Override
@ -43,17 +48,18 @@ public boolean canGetUpdates(String updatesDirName, Client client) {
} }
private boolean isWhitelisted(String property, ClientProfile profile, Client client) { private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if (client.permissions != null) { if(client.permissions != null) {
String permByUUID = property.formatted(profile.getUUID()); String permByUUID = String.format(property, profile.getUUID());
if (client.permissions.hasPerm(permByUUID)) { if(client.permissions.hasPerm(permByUUID)) {
return true; return true;
} }
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT)); String permByTitle = String.format(property, profile.getTitle().toLowerCase(Locale.ROOT));
if (client.permissions.hasPerm(permByTitle)) { if(client.permissions.hasPerm(permByTitle)) {
return true; return true;
} }
} }
List<String> allowedUsername = profileWhitelist.get(profile.getTitle()); List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
return allowedUsername != null && allowedUsername.contains(client.username); if (allowedUsername != null && allowedUsername.contains(client.username)) return true;
return false;
} }
} }

View file

@ -2,10 +2,8 @@
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import java.util.UUID;
public interface JoinServerProtectHandler { public interface JoinServerProtectHandler {
default boolean onJoinServer(String serverID, String username, UUID uuid, Client client) { default boolean onJoinServer(String serverID, String username, Client client) {
return true; return true;
} }
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.protect.interfaces; package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler { public interface ProfilesProtectHandler {

View file

@ -1,8 +1,8 @@
package pro.gravit.launchserver.auth.protect.interfaces; package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent; import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.SecurityReportRequestEvent; import pro.gravit.launcher.events.request.SecurityReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent; import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse; import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;

View file

@ -3,37 +3,35 @@
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.HTTPRequest;
import pro.gravit.launchserver.HttpRequester; import pro.gravit.launcher.Launcher;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.launcher.profiles.Texture;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class JsonTextureProvider extends TextureProvider { public class JsonTextureProvider extends TextureProvider {
private static final Type MAP_TYPE = new TypeToken<Map<String, JsonTexture>>() {
}.getType();
private transient final Logger logger = LogManager.getLogger();
private transient final HttpRequester requester = new HttpRequester();
public String url; public String url;
public String bearerToken; private transient final Logger logger = LogManager.getLogger();
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {}.getType();
@Override @Override
public void close() { public void close() throws IOException {
//None //None
} }
@Override @Override
public Texture getCloakTexture(UUID uuid, String username, String client) { public Texture getCloakTexture(UUID uuid, String username, String client) throws IOException {
logger.warn("Ineffective get cloak texture for {}", username); logger.warn("Ineffective get cloak texture for {}", username);
return getAssets(uuid, username, client).get("CAPE"); return getAssets(uuid, username, client).get("CAPE");
} }
@Override @Override
public Texture getSkinTexture(UUID uuid, String username, String client) { public Texture getSkinTexture(UUID uuid, String username, String client) throws IOException {
logger.warn("Ineffective get skin texture for {}", username); logger.warn("Ineffective get skin texture for {}", username);
return getAssets(uuid, username, client).get("SKIN"); return getAssets(uuid, username, client).get("SKIN");
} }
@ -41,28 +39,24 @@ public Texture getSkinTexture(UUID uuid, String username, String client) {
@Override @Override
public Map<String, Texture> getAssets(UUID uuid, String username, String client) { public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
try { try {
Map<String, JsonTexture> map = requester.<Map<String, JsonTexture>>send(requester.get(RequestTextureProvider.getTextureURL(url, uuid, username, client), bearerToken), MAP_TYPE).getOrThrow(); var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client)));
return JsonTexture.convertMap(map);
Map<String, Texture> map = Launcher.gsonManager.gson.fromJson(result, MAP_TYPE);
if(map == null) {
return new HashMap<>();
}
if(map.get("skin") != null) { // Legacy script
map.put("SKIN", map.get("skin"));
map.remove("skin");
}
if(map.get("cloak") != null) {
map.put("CAPE", map.get("cloak"));
map.remove("cloak");
}
return map;
} catch (IOException e) { } catch (IOException e) {
logger.error("JsonTextureProvider", e); logger.error("JsonTextureProvider", e);
return new HashMap<>(); return new HashMap<>();
} }
} }
public record JsonTexture(String url, String digest, Map<String, String> metadata) {
public Texture toTexture() {
return new Texture(url, digest == null ? null : SecurityHelper.fromHex(digest), metadata);
}
public static Map<String, Texture> convertMap(Map<String, JsonTexture> map) {
if (map == null) {
return new HashMap<>();
}
Map<String, Texture> res = new HashMap<>();
for(var e : map.entrySet()) {
res.put(e.getKey(), e.getValue().toTexture());
}
return res;
}
}
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture; package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.profiles.Texture;
import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.helper.VerifyHelper;
import java.io.IOException; import java.io.IOException;

View file

@ -1,7 +1,7 @@
package pro.gravit.launchserver.auth.texture; package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.profiles.Texture;
import pro.gravit.utils.helper.CommonHelper; import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
@ -29,7 +29,7 @@ public RequestTextureProvider(String skinURL, String cloakURL) {
private static Texture getTexture(String url, boolean cloak) throws IOException { private static Texture getTexture(String url, boolean cloak) throws IOException {
try { try {
return new Texture(url, cloak, null); return new Texture(url, cloak);
} catch (FileNotFoundException ignored) { } catch (FileNotFoundException ignored) {
return null; // Simply not found return null; // Simply not found
} }
@ -37,7 +37,7 @@ private static Texture getTexture(String url, boolean cloak) throws IOException
private static Texture getTexture(String url, Path local, boolean cloak) throws IOException { private static Texture getTexture(String url, Path local, boolean cloak) throws IOException {
try { try {
return new Texture(url, local, cloak, null); return new Texture(url, local, cloak);
} catch (FileNotFoundException ignored) { } catch (FileNotFoundException ignored) {
return null; // Simply not found return null; // Simply not found
} }
@ -60,8 +60,7 @@ public Texture getCloakTexture(UUID uuid, String username, String client) throws
if (cloakLocalPath == null) { if (cloakLocalPath == null) {
return getTexture(textureUrl, true); return getTexture(textureUrl, true);
} else { } else {
String path = getTextureURL(cloakLocalPath, uuid, username, client); return getTexture(textureUrl, Paths.get(cloakLocalPath), true);
return getTexture(textureUrl, Paths.get(path), true);
} }
} }
@ -71,8 +70,7 @@ public Texture getSkinTexture(UUID uuid, String username, String client) throws
if (skinLocalPath == null) { if (skinLocalPath == null) {
return getTexture(textureUrl, false); return getTexture(textureUrl, false);
} else { } else {
String path = getTextureURL(skinLocalPath, uuid, username, client); return getTexture(textureUrl, Paths.get(skinLocalPath), false);
return getTexture(textureUrl, Paths.get(path), false);
} }
} }
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture; package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.profiles.Texture;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
import java.io.IOException; import java.io.IOException;
@ -33,6 +33,17 @@ public static void registerProviders() {
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException; public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
@Deprecated @Deprecated
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) { public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
@ -72,24 +83,13 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
} }
Map<String, Texture> map = new HashMap<>(); Map<String, Texture> map = new HashMap<>();
if (skin != null) { if(skin != null) {
map.put("SKIN", skin); map.put("SKIN", skin);
} }
if (cloak != null) { if(cloak != null) {
map.put("CAPE", cloak); map.put("CAPE", cloak);
} }
return map; return map;
} }
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
} }

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.auth.texture; package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.profiles.Texture; import pro.gravit.launcher.profiles.Texture;
import java.util.UUID; import java.util.UUID;

View file

@ -1,186 +0,0 @@
package pro.gravit.launchserver.auth.updates;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Stream;
public class LocalUpdatesProvider extends UpdatesProvider {
private final transient Logger logger = LogManager.getLogger();
public String cacheFile = ".updates-cache";
public String updatesDir = "updates";
public boolean cacheUpdates = true;
private volatile transient Map<String, HashedDir> updatesDirMap;
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
public void readUpdatesFromCache() throws IOException {
readCache(Path.of(cacheFile));
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
@Override
public void init(LaunchServer server) {
super.init(server);
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
} catch (IOException e) {
logger.error("Updates not synced", e);
}
}
@Override
public void syncInitially() throws IOException {
readUpdatesDir();
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
@Override
public HashedDir getUpdatesDir(String updateName) {
return updatesDirMap.get(updateName);
}
private Path resolveUpdateName(String updateName) {
if(updateName == null) {
return Path.of(updatesDir);
}
return Path.of(updatesDir).resolve(updateName);
}
@Override
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) {
var target = path.resolve(e.getKey());
var source = e.getValue();
IOHelper.createParentDirs(target);
if(deleteAfterUpload) {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
} else {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@Override
public Map<String, Path> download(String updateName, List<String> files) {
var path = resolveUpdateName(updateName);
Map<String, Path> map = new HashMap<>();
for(var e : files) {
map.put(e, path.resolve(e));
}
return map;
}
@Override
public void delete(String updateName, List<String> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files) {
var target = path.resolve(e);
Files.delete(target);
}
}
@Override
public void delete(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
IOHelper.deleteDir(path, true);
}
@Override
public void create(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
Files.createDirectories(path);
}
}

View file

@ -1,52 +0,0 @@
package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class UpdatesProvider {
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalUpdatesProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public void sync() throws IOException {
sync(null);
}
public abstract void syncInitially() throws IOException;
public abstract void sync(Collection<String> updateNames) throws IOException;
public abstract HashedDir getUpdatesDir(String updateName);
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
public abstract Map<String, Path> download(String updateName, List<String> files);
public abstract void delete(String updateName, List<String> files) throws IOException;
public abstract void delete(String updateName) throws IOException;
public abstract void create(String updateName) throws IOException;
public void close() {
}
}

View file

@ -4,19 +4,24 @@
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask; import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.CommonHelper; import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public abstract class BinaryPipeline { public class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>(); public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final AtomicLong count = new AtomicLong(0);
public final Path buildDir; public final Path buildDir;
public final String nameFormat; public final String nameFormat;
protected transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) { public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir; this.buildDir = buildDir;
@ -67,19 +72,33 @@ public <T extends LauncherBuildTask> Optional<T> getTaskByClass(Class<T> taskCla
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst(); return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst();
} }
public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pred) { public void build(Path target, boolean deleteTempFiles) throws IOException {
LauncherBuildTask last = null; logger.info("Building launcher binary file");
for(var e : tasks) { count.set(0); // set jar number
if(pred.test(e)) { Path thisPath = null;
return Optional.ofNullable(last); boolean isNeedDelete = false;
} long time_start = System.currentTimeMillis();
last = e; long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
if (isNeedDelete && deleteTempFiles) Files.deleteIfExists(oldPath);
isNeedDelete = task.allowDelete();
logger.info("Task {} processed from {} millis", task.getName(), time_task);
} }
return Optional.empty(); long time_end = System.currentTimeMillis();
if (isNeedDelete && deleteTempFiles) IOHelper.move(thisPath, target);
else IOHelper.copy(thisPath, target);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
} }
public String nextName(String taskName) { public String nextName(String taskName) {
return nameFormat.formatted(taskName); return String.format(nameFormat, taskName, count.getAndIncrement());
} }
public Path nextPath(String taskName) { public Path nextPath(String taskName) {

View file

@ -2,9 +2,9 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.launcher.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject; import pro.gravit.launcher.serialize.stream.StreamObject;
import pro.gravit.launchserver.binary.tasks.MainBuildTask; import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
@ -45,18 +45,13 @@ public class BuildContext {
public final MainBuildTask task; public final MainBuildTask task;
public final HashSet<String> fileList; public final HashSet<String> fileList;
public final HashSet<String> clientModules; public final HashSet<String> clientModules;
public final HashSet<String> legacyClientModules;
private Path runtimeDir;
private boolean deleteRuntimeDir;
public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task, Path runtimeDir) { public BuildContext(ZipOutputStream output, List<JarFile> readerClassPath, MainBuildTask task) {
this.output = output; this.output = output;
this.readerClassPath = readerClassPath; this.readerClassPath = readerClassPath;
this.task = task; this.task = task;
this.runtimeDir = runtimeDir;
fileList = new HashSet<>(1024); fileList = new HashSet<>(1024);
clientModules = new HashSet<>(); clientModules = new HashSet<>();
legacyClientModules = new HashSet<>();
} }
public void pushFile(String filename, InputStream inputStream) throws IOException { public void pushFile(String filename, InputStream inputStream) throws IOException {
@ -106,14 +101,6 @@ public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<Stri
pushJarFile(jarfile.toUri().toURL(), filter, needTransform); pushJarFile(jarfile.toUri().toURL(), filter, needTransform);
} }
public Path getRuntimeDir() {
return runtimeDir;
}
public void setRuntimeDir(Path runtimeDir) {
this.runtimeDir = runtimeDir;
}
public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException { public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) { try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
ZipEntry e = input.getNextEntry(); ZipEntry e = input.getNextEntry();
@ -140,16 +127,6 @@ public void pushJarFile(URL jarfile, Predicate<ZipEntry> filter, Predicate<Strin
e = input.getNextEntry(); e = input.getNextEntry();
} }
} }
}
public boolean isDeleteRuntimeDir() {
return deleteRuntimeDir;
}
public void setDeleteRuntimeDir(boolean deleteRuntimeDir) {
this.deleteRuntimeDir = deleteRuntimeDir;
} }
private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> { private final static class RuntimeDirVisitor extends SimpleFileVisitor<Path> {
@ -234,8 +211,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
try { try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec); cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
InvalidAlgorithmParameterException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) { try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {

View file

@ -0,0 +1,17 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.exe.Launch4JTask;
public final class EXEL4JLauncherBinary extends LauncherBinary {
public EXEL4JLauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
}
@Override
public void init() {
tasks.add(new Launch4JTask(server));
}
}

View file

@ -9,7 +9,7 @@
public class EXELauncherBinary extends LauncherBinary { public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) { public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe"); super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s-%d.exe");
} }
@Override @Override

View file

@ -1,6 +1,6 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.*; import pro.gravit.launchserver.binary.tasks.*;
@ -8,28 +8,25 @@
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public final class JARLauncherBinary extends LauncherBinary { public final class JARLauncherBinary extends LauncherBinary {
public final AtomicLong count; public final AtomicLong count;
public final Path runtimeDir; public final Path runtimeDir;
public final Path guardDir;
public final Path buildDir; public final Path buildDir;
public final List<Path> coreLibs; public final List<Path> coreLibs;
public final List<Path> addonLibs; public final List<Path> addonLibs;
public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException { public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s.jar"); super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar");
count = new AtomicLong(0); count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
guardDir = server.dir.resolve(Launcher.GUARD_DIR);
buildDir = server.dir.resolve("build"); buildDir = server.dir.resolve("build");
coreLibs = new ArrayList<>(); coreLibs = new ArrayList<>();
addonLibs = new ArrayList<>(); addonLibs = new ArrayList<>();
files = new HashMap<>();
if (!Files.isDirectory(buildDir)) { if (!Files.isDirectory(buildDir)) {
Files.deleteIfExists(buildDir); Files.deleteIfExists(buildDir);
Files.createDirectory(buildDir); Files.createDirectory(buildDir);

View file

@ -1,14 +1,11 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public abstract class LauncherBinary extends BinaryPipeline { public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server; public final LaunchServer server;
@ -22,27 +19,11 @@ protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat
} }
public static Path resolve(LaunchServer server, String ext) { public static Path resolve(LaunchServer server, String ext) {
return Path.of(server.config.binaryName + ext); return server.config.copyBinaries ? server.updatesDir.resolve(server.config.binaryName + ext) : server.dir.resolve(server.config.binaryName + ext);
} }
public void build() throws IOException { public void build() throws IOException {
logger.info("Building launcher binary file"); build(syncBinaryFile, server.config.launcher.deleteTempFiles);
Path thisPath = null;
long time_start = System.currentTimeMillis();
long time_this = time_start;
for (LauncherBuildTask task : tasks) {
logger.info("Task {}", task.getName());
Path oldPath = thisPath;
thisPath = task.process(oldPath);
long time_task_end = System.currentTimeMillis();
long time_task = time_task_end - time_this;
time_this = time_task_end;
logger.info("Task {} processed from {} millis", task.getName(), time_task);
}
long time_end = System.currentTimeMillis();
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true);
IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start);
} }
public final boolean exists() { public final boolean exists() {
@ -56,14 +37,10 @@ public final byte[] getDigest() {
public void init() { public void init() {
} }
public final boolean sync() { public final boolean sync() throws IOException {
try { boolean exists = exists();
var target = syncBinaryFile.toString(); digest = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(syncBinaryFile)) : null;
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path)); return exists;
return true;
} catch (Throwable e) {
return false;
}
} }
} }

View file

@ -69,6 +69,7 @@ public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, Stri
* *
* @param filename name of the file to add (use forward slash as a path separator) * @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file * @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null} * @throws NullPointerException if any of the arguments is {@code null}
*/ */
public void addFileContents(String filename, byte[] contents) throws IOException { public void addFileContents(String filename, byte[] contents) throws IOException {
@ -81,6 +82,7 @@ public void addFileContents(String filename, byte[] contents) throws IOException
* *
* @param filename name of the file to add (use forward slash as a path separator) * @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file * @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null} * @throws NullPointerException if any of the arguments is {@code null}
*/ */
public void addFileContents(String filename, InputStream contents) throws IOException { public void addFileContents(String filename, InputStream contents) throws IOException {
@ -93,6 +95,7 @@ public void addFileContents(String filename, InputStream contents) throws IOExce
* *
* @param entry name of the file to add (use forward slash as a path separator) * @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file * @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null} * @throws NullPointerException if any of the arguments is {@code null}
*/ */
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException { public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
@ -105,6 +108,7 @@ public void addFileContents(ZipEntry entry, byte[] contents) throws IOException
* *
* @param entry name of the file to add (use forward slash as a path separator) * @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file * @param contents contents of the file
* @throws IOException
* @throws NullPointerException if any of the arguments is {@code null} * @throws NullPointerException if any of the arguments is {@code null}
*/ */
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException { public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
@ -130,6 +134,7 @@ public void addManifestAttribute(String name, String value) {
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the * Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
* underlying stream. * underlying stream.
* *
* @throws IOException
* @throws RuntimeException if the signing goes wrong * @throws RuntimeException if the signing goes wrong
*/ */
@Override @Override
@ -143,6 +148,7 @@ public void close() throws IOException {
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the * Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
* underlying stream open. * underlying stream open.
* *
* @throws IOException
* @throws RuntimeException if the signing goes wrong * @throws RuntimeException if the signing goes wrong
*/ */
public void finish() throws IOException { public void finish() throws IOException {
@ -199,6 +205,7 @@ private byte[] signSigFile(byte[] sigContents) throws Exception {
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature * Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
* file. * file.
* *
* @throws IOException
*/ */
private void writeManifest() throws IOException { private void writeManifest() throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN)); zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
@ -261,6 +268,7 @@ private byte[] writeSigFile() throws IOException {
/** /**
* Signs the .SIG file and writes the signature (.RSA file) to the JAR. * Signs the .SIG file and writes the signature (.RSA file) to the JAR.
* *
* @throws IOException
* @throws RuntimeException if the signing failed * @throws RuntimeException if the signing failed
*/ */
private void writeSignature(byte[] sigFile) throws IOException { private void writeSignature(byte[] sigFile) throws IOException {

View file

@ -54,7 +54,7 @@ public static void apply(Path inputFile, Path addFile, ZipOutputStream output, L
private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException { private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException {
ClassReader cr = new ClassReader(input); ClassReader cr = new ClassReader(input);
ClassNode cn = new ClassNode(); ClassNode cn = new ClassNode();
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG) : 0); cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) : ClassReader.SKIP_FRAMES);
ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw); cn.accept(cw);
return cw.toByteArray(); return cw.toByteArray();
@ -74,4 +74,9 @@ public Path process(Path inputFile) throws IOException {
return out; return out;
} }
@Override
public boolean allowDelete() {
return true;
}
} }

View file

@ -48,11 +48,6 @@ public Path process(Path inputFile) throws IOException {
} }
attach(output, inputFile, srv.launcherBinary.coreLibs); attach(output, inputFile, srv.launcherBinary.coreLibs);
attach(output, inputFile, jars); attach(output, inputFile, jars);
for(var entry : srv.launcherBinary.files.entrySet()) {
ZipEntry newEntry = IOHelper.newZipEntry(entry.getKey());
output.putNextEntry(newEntry);
IOHelper.transfer(entry.getValue(), output);
}
} }
return outputFile; return outputFile;
} }
@ -68,6 +63,11 @@ private boolean filter(String name) {
return exclusions.stream().anyMatch(name::startsWith); return exclusions.stream().anyMatch(name::startsWith);
} }
@Override
public boolean allowDelete() {
return true;
}
public List<Path> getJars() { public List<Path> getJars() {
return jars; return jars;
} }

View file

@ -81,4 +81,9 @@ public Path process(Path inputFile) throws IOException {
} }
return inputFile; return inputFile;
} }
@Override
public boolean allowDelete() {
return false;
}
} }

View file

@ -43,4 +43,9 @@ public Path process(Path inputFile) throws IOException {
} }
return output; return output;
} }
@Override
public boolean allowDelete() {
return true;
}
} }

View file

@ -7,4 +7,6 @@ public interface LauncherBuildTask {
String getName(); String getName();
Path process(Path inputFile) throws IOException; Path process(Path inputFile) throws IOException;
boolean allowDelete();
} }

View file

@ -8,8 +8,8 @@
import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.FieldNode;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.LauncherConfig; import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader; import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.InjectClassAcceptor; import pro.gravit.launchserver.asm.InjectClassAcceptor;
@ -51,12 +51,11 @@ public String getName() {
@Override @Override
public Path process(Path inputJar) throws IOException { public Path process(Path inputJar) throws IOException {
Path outputJar = server.launcherBinary.nextPath(this); Path outputJar = server.launcherBinary.nextPath("main");
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) { try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir); BuildContext context = new BuildContext(output, reader.getCp(), this);
initProps(); initProps();
preBuildHook.hook(context); preBuildHook.hook(context);
properties.put("launcher.legacymodules", context.legacyClientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList())); properties.put("launcher.modules", context.clientModules.stream().map(e -> Type.getObjectType(e.replace('.', '/'))).collect(Collectors.toList()));
postInitProps(); postInitProps();
reader.getCp().add(new JarFile(inputJar.toFile())); reader.getCp().add(new JarFile(inputJar.toFile()));
@ -69,13 +68,11 @@ public Path process(Path inputJar) throws IOException {
Map<String, byte[]> runtime = new HashMap<>(256); Map<String, byte[]> runtime = new HashMap<>(256);
// Write launcher guard dir // Write launcher guard dir
if (server.config.launcher.encryptRuntime) { if (server.config.launcher.encryptRuntime) {
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false); context.pushEncryptedDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
} else { } else {
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false); context.pushDir(server.launcherBinary.runtimeDir, Launcher.RUNTIME_DIR, runtime, false);
}
if(context.isDeleteRuntimeDir()) {
IOHelper.deleteDir(context.getRuntimeDir(), true);
} }
context.pushDir(server.launcherBinary.guardDir, Launcher.GUARD_DIR, runtime, false);
LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName); LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName);
context.pushFile(Launcher.CONFIG_FILE, launcherConfig); context.pushFile(Launcher.CONFIG_FILE, launcherConfig);
@ -111,6 +108,7 @@ protected void initProps() {
properties.put("launcher.projectName", server.config.projectName); properties.put("launcher.projectName", server.config.projectName);
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey()); properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512)); properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
properties.put("launcher.guardType", server.config.launcher.guardType);
properties.put("launchercore.env", server.config.env); properties.put("launchercore.env", server.config.env);
properties.put("launcher.memory", server.config.launcher.memoryLimit); properties.put("launcher.memory", server.config.launcher.memoryLimit);
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions); properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
@ -126,10 +124,10 @@ protected void initProps() {
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt)); server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash)); properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
properties.put("runtimeconfig.secureCheckSalt", launcherSalt); properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken(); //LogHelper.debug("[checkSecure] %s: %s", launcherSalt, Arrays.toString(launcherSecureHash));
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret); if (server.runtime.oemUnlockKey == null) server.runtime.oemUnlockKey = SecurityHelper.randomStringToken();
server.runtime.buildNumber++; properties.put("runtimeconfig.oemUnlockKey", server.runtime.oemUnlockKey);
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
} }
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) { public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
@ -146,7 +144,7 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
asmTransformer.transform(cn, classname, context); asmTransformer.transform(cn, classname, context);
continue; continue;
} else if (cn != null) { } else if (cn != null) {
writer = new SafeClassWriter(reader, 0); writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(writer); cn.accept(writer);
result = writer.toByteArray(); result = writer.toByteArray();
} }
@ -157,13 +155,18 @@ public byte[] transformClass(byte[] bytes, String classname, BuildContext contex
} }
} }
if (cn != null) { if (cn != null) {
writer = new SafeClassWriter(reader, 0); writer = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(writer); cn.accept(writer);
result = writer.toByteArray(); result = writer.toByteArray();
} }
return result; return result;
} }
@Override
public boolean allowDelete() {
return true;
}
@FunctionalInterface @FunctionalInterface
public interface Transformer { public interface Transformer {
byte[] transform(byte[] input, String classname, BuildContext context); byte[] transform(byte[] input, String classname, BuildContext context);
@ -175,7 +178,7 @@ default byte[] transform(byte[] input, String classname, BuildContext context) {
ClassNode cn = new ClassNode(); ClassNode cn = new ClassNode();
reader.accept(cn, 0); reader.accept(cn, 0);
transform(cn, classname, context); transform(cn, classname, context);
SafeClassWriter writer = new SafeClassWriter(context.task.reader, 0); SafeClassWriter writer = new SafeClassWriter(context.task.reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(writer); cn.accept(writer);
return writer.toByteArray(); return writer.toByteArray();
} }

View file

@ -7,11 +7,12 @@
import pro.gravit.utils.helper.UnpackHelper; import pro.gravit.utils.helper.UnpackHelper;
import java.io.IOException; import java.io.IOException;
import java.nio.file.*; import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PrepareBuildTask implements LauncherBuildTask { public class PrepareBuildTask implements LauncherBuildTask {
private final LaunchServer server; private final LaunchServer server;
@ -32,28 +33,21 @@ public String getName() {
public Path process(Path inputFile) throws IOException { public Path process(Path inputFile) throws IOException {
server.launcherBinary.coreLibs.clear(); server.launcherBinary.coreLibs.clear();
server.launcherBinary.addonLibs.clear(); server.launcherBinary.addonLibs.clear();
server.launcherBinary.files.clear(); IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), true);
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false); IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), true);
if(Files.isDirectory(server.launcherLibrariesCompile)) {
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
}
try(Stream<Path> stream = Files.walk(server.launcherPack, FileVisitOption.FOLLOW_LINKS).filter((e) -> {
try {
return !Files.isDirectory(e) && !Files.isHidden(e);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
})) {
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString().replace("\\", "/"), (v) -> v));
server.launcherBinary.files.putAll(map);
}
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result); UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
tryUnpack(); tryUnpack();
return result; return result;
} }
@Override
public boolean allowDelete() {
return false;
}
public void tryUnpack() throws IOException { public void tryUnpack() throws IOException {
logger.info("Unpacking launcher native guard list and runtime"); logger.info("Unpacking launcher native guard list and runtime");
UnpackHelper.unpackZipNoCheck("guard.zip", server.launcherBinary.guardDir);
UnpackHelper.unpackZipNoCheck("runtime.zip", server.launcherBinary.runtimeDir); UnpackHelper.unpackZipNoCheck("runtime.zip", server.launcherBinary.runtimeDir);
} }

View file

@ -26,7 +26,7 @@
public class SignJarTask implements LauncherBuildTask { public class SignJarTask implements LauncherBuildTask {
private static final Logger logger = LogManager.getLogger(); private transient static final Logger logger = LogManager.getLogger();
private final LaunchServerConfig.JarSignerConf config; private final LaunchServerConfig.JarSignerConf config;
private final LaunchServer srv; private final LaunchServer srv;
@ -40,7 +40,7 @@ public static CMSSignedDataGenerator gen(LaunchServerConfig.JarSignerConf config
return SignHelper.createSignedDataGenerator(c, return SignHelper.createSignedDataGenerator(c,
config.keyAlias, config.signAlgo, config.keyPass); config.keyAlias, config.signAlgo, config.keyPass);
} catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException } catch (CertificateEncodingException | UnrecoverableKeyException | KeyStoreException
| OperatorCreationException | NoSuchAlgorithmException | CMSException e) { | OperatorCreationException | NoSuchAlgorithmException | CMSException e) {
logger.error("Create signedDataGenerator failed", e); logger.error("Create signedDataGenerator failed", e);
return null; return null;
} }
@ -104,4 +104,9 @@ private void autoSign(Path inputFile, Path signedFile) throws IOException {
} }
} }
} }
@Override
public boolean allowDelete() {
return true;
}
} }

View file

@ -0,0 +1,131 @@
package pro.gravit.launchserver.binary.tasks.exe;
import net.sf.launch4j.Builder;
import net.sf.launch4j.Log;
import net.sf.launch4j.config.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
public class Launch4JTask implements LauncherBuildTask, BuildExeMainTask {
public static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle
private static final String VERSION = Version.getVersion().getVersionString();
private static final int BUILD = Version.getVersion().build;
private final Path faviconFile;
private final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
public Launch4JTask(LaunchServer launchServer) {
this.server = launchServer;
faviconFile = launchServer.dir.resolve("favicon.ico");
}
public static String formatVars(String mask) {
return String.format(mask, VERSION, BUILD);
}
@Override
public String getName() {
return "launch4j";
}
@Override
public Path process(Path inputFile) throws IOException {
logger.info("Building launcher EXE binary file (Using Launch4J)");
Path output = setConfig();
// Set favicon path
Config config = ConfigPersister.getInstance().getConfig();
if (IOHelper.isFile(faviconFile))
config.setIcon(faviconFile.toFile());
else {
config.setIcon(null);
logger.warn("Missing favicon.ico file");
}
// Start building
Builder builder = new Builder(Launch4JLog.INSTANCE);
try {
builder.build();
} catch (Throwable e) {
throw new IOException(e);
}
return output;
}
@Override
public boolean allowDelete() {
return true;
}
private Path setConfig() {
Path path = server.launcherEXEBinary.nextPath(getName());
Config config = new Config();
// Set file options
config.setChdir(".");
config.setErrTitle("JVM Error");
config.setDownloadUrl(server.config.launch4j.downloadUrl);
if (server.config.launch4j.supportURL != null) config.setSupportUrl(server.config.launch4j.supportURL);
// Set boolean options
config.setPriorityIndex(0);
config.setHeaderType(Config.GUI_HEADER);
config.setStayAlive(false);
config.setRestartOnCrash(false);
// Prepare JRE
Jre jre = new Jre();
jre.setMinVersion(server.config.launch4j.minVersion);
if (server.config.launch4j.setMaxVersion)
jre.setMaxVersion(server.config.launch4j.maxVersion);
jre.setRuntimeBits(Jre.RUNTIME_BITS_64_AND_32);
jre.setJdkPreference(Jre.JDK_PREFERENCE_PREFER_JRE);
config.setJre(jre);
// Prepare version info (product)
VersionInfo info = new VersionInfo();
info.setProductName(server.config.launch4j.productName);
info.setProductVersion(formatVars(server.config.launch4j.productVer));
info.setFileDescription(server.config.launch4j.fileDesc);
info.setFileVersion(formatVars(server.config.launch4j.fileVer));
info.setCopyright(server.config.launch4j.copyright);
info.setTrademarks(server.config.launch4j.trademarks);
info.setInternalName(formatVars(server.config.launch4j.internalName));
// Prepare version info (file)
info.setTxtFileVersion(formatVars(server.config.launch4j.txtFileVersion));
info.setTxtProductVersion(formatVars(server.config.launch4j.txtProductVersion));
// Prepare version info (misc)
info.setOriginalFilename(path.getFileName().toString());
info.setLanguage(LanguageID.RUSSIAN);
config.setVersionInfo(info);
// Set JAR wrapping options
config.setDontWrapJar(false);
config.setJar(server.launcherBinary.syncBinaryFile.toFile());
config.setOutfile(path.toFile());
// Return prepared config
ConfigPersister.getInstance().setAntConfig(config, null);
return path;
}
private final static class Launch4JLog extends Log {
private static final Launch4JLog INSTANCE = new Launch4JLog();
private static final Logger logger = LogManager.getLogger();
@Override
public void append(String s) {
logger.info(s);
}
@Override
public void clear() {
// Do nothing
}
}
}

View file

@ -3,11 +3,9 @@
import me.tongfei.progressbar.ProgressBar; import me.tongfei.progressbar.ProgressBar;
import me.tongfei.progressbar.ProgressBarBuilder; import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle; import me.tongfei.progressbar.ProgressBarStyle;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.command.CommandException; import pro.gravit.utils.Downloader;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
@ -31,25 +29,15 @@ public Command(Map<String, pro.gravit.utils.command.Command> childCommands, Laun
this.server = server; this.server = server;
} }
protected ClientProfile.Version parseClientVersion(String arg) throws CommandException {
if(arg.isEmpty()) {
throw new CommandException("ClientVersion can't be empty");
}
return Launcher.gsonManager.gson.fromJson(arg, ClientProfile.Version.class);
}
protected boolean showApplyDialog(String text) throws IOException { protected boolean showApplyDialog(String text) throws IOException {
System.out.printf("%s [Y/N]:", text); System.out.printf("%s [Y/N]:", text);
String response = server.commandHandler.readLine().toLowerCase(Locale.ROOT); String response = server.commandHandler.readLine().toLowerCase(Locale.ROOT);
return response.equals("y"); return response.equals("y");
} }
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception { protected Downloader downloadWithProgressBar(String taskName, List<AsyncDownloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
long total = 0; long total = 0;
for (Downloader.SizedFile file : list) { for (AsyncDownloader.SizedFile file : list) {
if(file.size < 0) {
continue;
}
total += file.size; total += file.size;
} }
long totalFiles = list.size(); long totalFiles = list.size();
@ -61,7 +49,7 @@ protected Downloader downloadWithProgressBar(String taskName, List<Downloader.Si
.setStyle(ProgressBarStyle.COLORFUL_UNICODE_BLOCK) .setStyle(ProgressBarStyle.COLORFUL_UNICODE_BLOCK)
.setUnit("MB", 1024 * 1024) .setUnit("MB", 1024 * 1024)
.build(); .build();
bar.setExtraMessage(" [0/%d]".formatted(totalFiles)); bar.setExtraMessage(String.format(" [0/%d]", totalFiles));
Downloader downloader = Downloader.downloadList(list, baseUrl, targetDir, new Downloader.DownloadCallback() { Downloader downloader = Downloader.downloadList(list, baseUrl, targetDir, new Downloader.DownloadCallback() {
@Override @Override
public void apply(long fullDiff) { public void apply(long fullDiff) {
@ -71,7 +59,7 @@ public void apply(long fullDiff) {
@Override @Override
public void onComplete(Path path) { public void onComplete(Path path) {
bar.setExtraMessage(" [%d/%d]".formatted(currentFiles.incrementAndGet(), totalFiles)); bar.setExtraMessage(String.format(" [%d/%d]", currentFiles.incrementAndGet(), totalFiles));
} }
}, null, 4); }, null, 4);
downloader.getFuture().handle((v, e) -> { downloader.getFuture().handle((v, e) -> {

View file

@ -10,8 +10,7 @@
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
public class DebugCommand extends Command { public class DebugCommand extends Command {
private final transient Logger logger = LogManager.getLogger(); private transient Logger logger = LogManager.getLogger();
public DebugCommand(LaunchServer server) { public DebugCommand(LaunchServer server) {
super(server); super(server);
} }
@ -32,11 +31,10 @@ public void invoke(String... args) throws Exception {
boolean value = Boolean.parseBoolean(args[0]); boolean value = Boolean.parseBoolean(args[0]);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false); LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration(); Configuration config = ctx.getConfiguration();
config.getWatchManager().setIntervalSeconds(-1);
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit"); LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG); loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
ctx.updateLoggers(); ctx.updateLoggers();
if (value) { if(value) {
logger.info("Log level TRACE enabled"); logger.info("Log level TRACE enabled");
} else { } else {
logger.info("Log level TRACE disabled"); logger.info("Log level TRACE disabled");

View file

@ -0,0 +1,25 @@
package pro.gravit.launchserver.command.basic;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public final class RestartCommand extends Command {
public RestartCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Restart LaunchServer";
}
@Override
public void invoke(String... args) {
server.fullyRestart();
}
}

View file

@ -5,17 +5,14 @@
import pro.gravit.launchserver.command.hash.*; import pro.gravit.launchserver.command.hash.*;
import pro.gravit.launchserver.command.modules.LoadModuleCommand; import pro.gravit.launchserver.command.modules.LoadModuleCommand;
import pro.gravit.launchserver.command.modules.ModulesCommand; import pro.gravit.launchserver.command.modules.ModulesCommand;
import pro.gravit.launchserver.command.profiles.ProfilesCommand;
import pro.gravit.launchserver.command.service.*; import pro.gravit.launchserver.command.service.*;
import pro.gravit.launchserver.command.sync.*;
import pro.gravit.launchserver.command.tools.SignDirCommand;
import pro.gravit.launchserver.command.tools.SignJarCommand;
import pro.gravit.utils.command.BaseCommandCategory; import pro.gravit.utils.command.BaseCommandCategory;
import pro.gravit.utils.command.basic.ClearCommand; import pro.gravit.utils.command.basic.ClearCommand;
import pro.gravit.utils.command.basic.GCCommand; import pro.gravit.utils.command.basic.GCCommand;
import pro.gravit.utils.command.basic.HelpCommand; import pro.gravit.utils.command.basic.HelpCommand;
public abstract class CommandHandler extends pro.gravit.utils.command.CommandHandler { public abstract class CommandHandler extends pro.gravit.utils.command.CommandHandler {
@SuppressWarnings("deprecation")
public static void registerCommands(pro.gravit.utils.command.CommandHandler handler, LaunchServer server) { public static void registerCommands(pro.gravit.utils.command.CommandHandler handler, LaunchServer server) {
BaseCommandCategory basic = new BaseCommandCategory(); BaseCommandCategory basic = new BaseCommandCategory();
// Register basic commands // Register basic commands
@ -23,6 +20,7 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
basic.registerCommand("version", new VersionCommand(server)); basic.registerCommand("version", new VersionCommand(server));
basic.registerCommand("build", new BuildCommand(server)); basic.registerCommand("build", new BuildCommand(server));
basic.registerCommand("stop", new StopCommand(server)); basic.registerCommand("stop", new StopCommand(server));
basic.registerCommand("restart", new RestartCommand(server));
basic.registerCommand("debug", new DebugCommand(server)); basic.registerCommand("debug", new DebugCommand(server));
basic.registerCommand("clear", new ClearCommand(handler)); basic.registerCommand("clear", new ClearCommand(handler));
basic.registerCommand("gc", new GCCommand()); basic.registerCommand("gc", new GCCommand());
@ -37,8 +35,12 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server)); updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server)); updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server)); updates.registerCommand("downloadClient", new DownloadClientCommand(server));
updates.registerCommand("sync", new SyncCommand(server)); updates.registerCommand("syncBinaries", new SyncBinariesCommand(server));
updates.registerCommand("profile", new ProfilesCommand(server)); updates.registerCommand("syncUpdates", new SyncUpdatesCommand(server));
updates.registerCommand("syncProfiles", new SyncProfilesCommand(server));
updates.registerCommand("syncUP", new SyncUPCommand(server));
updates.registerCommand("saveProfiles", new SaveProfilesCommand(server));
updates.registerCommand("makeProfile", new MakeProfileCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management"); Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");
handler.registerCategory(updatesCategory); handler.registerCategory(updatesCategory);
@ -49,16 +51,11 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
service.registerCommand("notify", new NotifyCommand(server)); service.registerCommand("notify", new NotifyCommand(server));
service.registerCommand("component", new ComponentCommand(server)); service.registerCommand("component", new ComponentCommand(server));
service.registerCommand("clients", new ClientsCommand(server)); service.registerCommand("clients", new ClientsCommand(server));
service.registerCommand("signJar", new SignJarCommand(server));
service.registerCommand("signDir", new SignDirCommand(server));
service.registerCommand("securitycheck", new SecurityCheckCommand(server)); service.registerCommand("securitycheck", new SecurityCheckCommand(server));
service.registerCommand("token", new TokenCommand(server)); service.registerCommand("token", new TokenCommand(server));
Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components"); Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components");
handler.registerCategory(serviceCategory); handler.registerCategory(serviceCategory);
//Register tools commands
BaseCommandCategory tools = new BaseCommandCategory();
tools.registerCommand("signJar", new SignJarCommand(server));
tools.registerCommand("signDir", new SignDirCommand(server));
Category toolsCategory = new Category(tools, "tools", "Other tools");
handler.registerCategory(toolsCategory);
} }
} }

View file

@ -1,26 +1,16 @@
package pro.gravit.launchserver.command.hash; package pro.gravit.launchserver.command.hash;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
public final class DownloadAssetCommand extends Command { public final class DownloadAssetCommand extends Command {
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public DownloadAssetCommand(LaunchServer server) { public DownloadAssetCommand(LaunchServer server) {
@ -29,7 +19,7 @@ public DownloadAssetCommand(LaunchServer server) {
@Override @Override
public String getArgsDescription() { public String getArgsDescription() {
return "[version] [dir] (mojang/mirror)"; return "[version] [dir]";
} }
@Override @Override
@ -39,97 +29,23 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 1); verifyArgs(args, 2);
//Version version = Version.byName(args[0]); //Version version = Version.byName(args[0]);
String versionName = args[0]; String versionName = args[0];
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets"); String dirName = IOHelper.verifyFileName(args[1]);
String type = args.length > 2 ? args[2] : "mojang";
Path assetDir = server.updatesDir.resolve(dirName); Path assetDir = server.updatesDir.resolve(dirName);
// Create asset dir // Create asset dir
if (Files.notExists(assetDir)) { logger.info("Creating asset dir: '{}'", dirName);
logger.info("Creating asset dir: '{}'", dirName); Files.createDirectory(assetDir);
Files.createDirectory(assetDir);
}
if (type.equals("mojang")) { // Download required asset
HttpRequester requester = new HttpRequester(); logger.info("Downloading asset, it may take some time");
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL); //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow(); server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
String profileUrl = null;
for (var e : versions.versions) {
if (e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
if (profileUrl == null) {
logger.error("Version {} not found", versionName);
return;
}
logger.info("Fetch profile {} from {}", versionName, profileUrl);
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json");
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
JsonObject objects = assets.get("objects").getAsJsonObject();
try (Writer writer = IOHelper.newWriter(indexPath)) {
logger.info("Save {}", indexPath);
Launcher.gsonManager.configGson.toJson(assets, writer);
}
if (!assetIndex.equals(versionName)) {
Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json");
logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
var target = assetDir.resolve(path);
if (Files.exists(target)) {
long fileSize = Files.size(target);
if (fileSize != size) {
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
} else {
continue;
}
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
logger.info("Download {} files", toDownload.size());
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
} else {
// Download required asset
logger.info("Downloading asset, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
}
// Finished // Finished
server.syncUpdatesDir(Collections.singleton(dirName)); server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Asset successfully downloaded: '{}'", dirName); logger.info("Asset successfully downloaded: '{}'", dirName);
} }
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
} }

View file

@ -3,17 +3,17 @@
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper; import pro.gravit.launchserver.helper.MakeProfileHelper;
import pro.gravit.utils.command.CommandException; import pro.gravit.utils.command.CommandException;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.UUID; import java.util.UUID;
@ -41,14 +41,18 @@ public void invoke(String... args) throws IOException, CommandException {
verifyArgs(args, 2); verifyArgs(args, 2);
//Version version = Version.byName(args[0]); //Version version = Version.byName(args[0]);
String versionName = args[0]; String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]); String dirName = IOHelper.verifyFileName(args[1]);
Path clientDir = server.updatesDir.resolve(dirName); Path clientDir = server.updatesDir.resolve(args[1]);
boolean isMirrorClientDownload = false; boolean isMirrorClientDownload = false;
if (args.length > 2) { if (args.length > 2) {
isMirrorClientDownload = args[2].equals("mirror"); isMirrorClientDownload = args[2].equals("mirror");
} }
// Create client dir
logger.info("Creating client dir: '{}'", dirName);
Files.createDirectory(clientDir);
// Download required client // Download required client
logger.info("Downloading client, it may take some time"); logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir); //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
@ -56,47 +60,43 @@ public void invoke(String... args) throws IOException, CommandException {
// Create profile file // Create profile file
logger.info("Creaing profile file: '{}'", dirName); logger.info("Creaing profile file: '{}'", dirName);
ClientProfile clientProfile = null; ClientProfile client = null;
if (isMirrorClientDownload) {
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorClientDownload = false;
}
}
if (!isMirrorClientDownload) { if (!isMirrorClientDownload) {
try { try {
String internalVersion = versionName; String internalVersion = versionName;
if (internalVersion.contains("-")) { if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-')); internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
} }
ClientProfile.Version version = ClientProfile.Version.of(internalVersion); ClientProfile.Version version = ClientProfile.Version.byName(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) { if (version.compareTo(ClientProfile.Version.MC164) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk"); logger.warn("Minecraft 1.6.4 and below not supported. Use at your own risk");
} }
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version); MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) { for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName()); logger.debug("Detected option {}", option.getClass().getSimpleName());
} }
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options); client = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) { } catch (Throwable e) {
isMirrorClientDownload = true; isMirrorClientDownload = true;
} }
} }
server.config.profileProvider.addProfile(clientProfile); if (isMirrorClientDownload) {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
client = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
client.setTitle(dirName);
client.setDir(dirName);
client.setUUID(UUID.randomUUID());
if (client.getServers() != null) {
ClientProfile.ServerProfile serverProfile = client.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
}
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
dirName, "json"))) {
Launcher.gsonManager.configGson.toJson(client, writer);
}
// Finished // Finished
server.syncProfilesDir(); server.syncProfilesDir();

View file

@ -3,7 +3,7 @@
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.CommandException; import pro.gravit.utils.command.CommandException;

Some files were not shown because too many files have changed in this diff Show more