Compare commits

..

No commits in common. "master" and "v4.0.0b1-dev" have entirely different histories.

777 changed files with 19378 additions and 34148 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

@ -1,83 +0,0 @@
name: push
on: push
jobs:
launcher:
name: Launcher
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Cache Gradle
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: gravit-${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-launcher
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5
- name: Create artifacts
run: |
mkdir -p artifacts/modules
cd LaunchServer/build/libs
mv proguard proguard-libraries
zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
cp LaunchServer.jar ../../../artifacts/LaunchServer.jar
cd ../../..
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 modules/*_module/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
uses: actions/upload-artifact@v4
with:
name: Launcher
path: artifacts
- name: Get version value, set to env
if: startsWith(github.event.ref, 'refs/tags')
run: echo "LAUNCHER_VERSION=$(echo ${{ github.event.ref }} | awk -F\/ '{print $3}')" >> $GITHUB_ENV
- name: Prebuild release files
if: startsWith(github.event.ref, 'refs/tags')
run: |
cd artifacts
zip -r -9 Release.zip *
zip -j -9 LaunchServerModules.zip ../modules/*_module/build/libs/*.jar
zip -j -9 LauncherModules.zip ../modules/*_lmodule/build/libs/*.jar
cd ../LaunchServer/build/libs
zip -r -9 ../../../artifacts/LauncherBase.zip * -x "LaunchServer-clean.jar"
- name: Create release
id: create_release
uses: softprops/action-gh-release@v2
if: startsWith(github.event.ref, 'refs/tags')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Список настроек тута: https://github.com/softprops/action-gh-release#-customizing
# Можно сделать пуш описания релиза из файла
with:
name: GravitLauncher ${{ env.LAUNCHER_VERSION }}
draft: false
prerelease: false
files: |
artifacts/*

3
.gitignore vendored
View file

@ -107,6 +107,3 @@ buildnumber
*.directory *.directory
cmd.bat cmd.bat
cmd.sh cmd.sh
project/target
## PVS Studio
.PVS-Studio/

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "modules"]
path = modules
url = https://github.com/GravitLauncher/LauncherModules.git

12
.travis.yml Normal file
View file

@ -0,0 +1,12 @@
language: java
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
script:
- ./gradlew assemble build
addons:
artifacts: true

View file

@ -1,47 +0,0 @@
# GravitLauncher GitConvention #
Цель конвенции — внедрить простые, прозрачные и эффективные правила работы с Git.
Разработка GravitLauncher идёт на базе [Git Flow](https://leanpub.com/git-flow/read). Подробности ниже.
## Ветвление ##
| Название ветки | Значение ветки | Исходная ветка | Пример ветки |
| ------------- | ------------- | ------------- | ------------- |
| **master** | Полностью готовая для production-а | **release** | |
| **develop** | Разработка нового функционала | **master** | |
| **release** | Тестирование всего нового функционала | **develop** | |
| | | | |
| **bugfix-*** | Исправляет баг нового функционала | **release** | *bugfix-auth* |
| **feature-*** | Добавляет новую возможность | **develop** | *feature-auth* |
| **hotfix-*** | Вносит срочное исправление для production-а | **master** | *hotfix-auth* |
-----
![Image of GitFlow](https://i.ytimg.com/vi/w2r0oLFtXAw/maxresdefault.jpg)
-----
## Коммиты ##
**Основные правила:**
1. Все коммиты должны быть на английском языке.
2. Запрещено использовать прошедшее время.
3. Обязательно должен быть использован префикс.
4. В конце не должно быть лишнего знака препинания.
5. Длина любой части не должна превышать 100 символов.
**Структура:**
```
[Префикс] <Сообщение>
```
| Префикс | Значение | Пример |
| ------- | -------- | ------ |
| **[FIX]** | Всё, что касается исправления багов | [FIX] Bug with failed authorization |
| **[DOCS]** | Всё, что касается документации | [DOCS] Documenting Authorization API |
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA on authorization |
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Typos in the authorization module |
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Switching to EDA in the authorization module |
| **[TEST]** | Всё, что касается тестирования | [TEST] Coverage of the authorization module with tests |
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Connecting Travis CI |

View file

@ -1,12 +0,0 @@
FROM ubuntu:latest
RUN apt-get update && apt-get install -y osslsigncode openjdk-11-jdk unzip jq screen
ADD https://download2.gluonhq.com/openjfx/11.0.2/openjfx-11.0.2_linux-x64_bin-jmods.zip .
RUN unzip openjfx-11.0.2_linux-x64_bin-jmods.zip && mv javafx-jmods-11.0.2/* /usr/lib/jvm/java-11-openjdk-amd64/jmods/ && rmdir javafx-jmods-11.0.2 && rm openjfx-11.0.2_linux-x64_bin-jmods.zip
RUN mkdir ./libraries ./launcher-libraries ./launcher-libraries-compile ./compat ./compat/modules
COPY ./LaunchServer/build/libs/LaunchServer.jar .
COPY ./LaunchServer/build/libs/libraries ./libraries
COPY ./LaunchServer/build/libs/launcher-libraries ./launcher-libraries
COPY ./LaunchServer/build/libs/launcher-libraries-compile ./launcher-libraries-compile
COPY ./compat/authlib/authlib-clean.jar ./LauncherAuthlib/build/libs/* ./ServerWrapper/build/libs/ServerWrapper.jar ./compat/
COPY ./modules/*_module/build/libs/* ./modules/*_lmodule/build/libs/* ./compat/modules/
CMD screen -DmS launchserver java -javaagent:LaunchServer.jar -jar LaunchServer.jar

View file

@ -1,199 +1,86 @@
def mainClassName = "pro.gravit.launchserver.Main" def mainClassName = "ru.gravit.launchserver.LaunchServer"
def mainAgentName = "pro.gravit.launchserver.StarterAgent" def mainAgentName = "ru.gravit.launchserver.StarterAgent"
evaluationDependsOn(':Launcher')
repositories { repositories {
maven { maven {
url "https://oss.sonatype.org/content/repositories/snapshots" url "https://hub.spigotmc.org/nexus/content/repositories/snapshots"
} }
maven { maven {
url "https://jcenter.bintray.com/" url "http://maven.geomajas.org/"
} }
maven { maven {
url "https://jitpack.io/" url "https://oss.sonatype.org/content/repositories/snapshots"
} }
maven { maven {
url 'https://maven.gravit-support.ru/repository/jitpack' url "http://repo.md-5.net/content/groups/public"
credentials { }
username = 'gravitlauncher'
password = 'gravitlauncher'
}
}
} }
sourceCompatibility = '21' sourceCompatibility = '1.8'
targetCompatibility = '21' targetCompatibility = '1.8'
configurations { configurations {
bundleOnly bundleOnly
bundle bundle
pack hikari
proguardPack bundle.extendsFrom bundleOnly
bundleOnly.extendsFrom bundle compileOnly.extendsFrom bundle, hikari
api.extendsFrom bundle, pack
} }
jar { jar {
dependsOn parent.childProjects.Launcher.tasks.assemble dependsOn parent.childProjects.Launcher.tasks.build, parent.childProjects.Launcher.tasks.genRuntimeJS, parent.childProjects.Launcher.tasks.jar
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } } from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
exclude("module-info.class") from(parent.childProjects.Launcher.tasks.jar.archivePath, parent.childProjects.Launcher.tasks.genRuntimeJS.archivePath)
from(parent.childProjects.Launcher.tasks.shadowJar) manifest.attributes("Main-Class": mainClassName,
from(parent.childProjects.Launcher.tasks.genRuntimeJS) "Premain-Class": mainAgentName,
manifest.attributes("Main-Class": mainClassName, "Can-Redefine-Classes": "true",
"Premain-Class": mainAgentName, "Can-Retransform-Classes": "true",
"Multi-Release": "true", "Can-Set-Native-Method-Prefix": "true"
"Automatic-Module-Name": "launchserver" )
)
} }
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
tasks.register('sourcesJar', Jar) {
from sourceSets.main.allJava
archiveClassifier.set('sources')
}
tasks.register('javadocJar', Jar) {
from javadoc
archiveClassifier.set('javadoc')
}
tasks.register('cleanjar', Jar) {
dependsOn jar
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName,
"Automatic-Module-Name": "launchserver"
)
from sourceSets.main.output
}
dependencies { dependencies {
pack(project(':LauncherAPI')) { compile project(':libLauncher') // pack
exclude group: "com.google.code.gson" compileOnly 'org.spigotmc:spigot-api:1.8-R0.1-SNAPSHOT' // api
} compileOnly 'net.md-5:bungeecord-api:1.8-SNAPSHOT' // api
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson'] compileOnly 'org.ow2.asm:asm-debug-all:5.0.4'
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1' bundleOnly 'org.ow2.asm:asm-all:5.0.4'
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] bundle 'org.apache.logging.log4j:log4j-core:2.9.0'
bundle group: 'org.jline', name: 'jline-native', version: rootProject['verJline'] bundle 'mysql:mysql-connector-java:8.0.12'
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline'] bundle 'jline:jline:2.14.6'
bundle group: 'org.jline', name: 'jline-terminal-ffm', version: rootProject['verJline'] bundle 'net.sf.proguard:proguard-base:6.0.3'
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix'] bundle 'org.bouncycastle:bcpkix-jdk15on:1.49'
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix'] bundle 'org.fusesource.jansi:jansi:1.17.1'
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm'] bundle 'commons-io:commons-io:2.6'
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty'] bundle 'org.javassist:javassist:3.23.1-GA'
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty'] bundle 'io.netty:netty-all:4.1.28.Final'
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 'org.slf4j:slf4j-simple:1.7.25'
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty'] bundle 'org.slf4j:slf4j-api:1.7.25'
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: 'com.mysql', name: 'mysql-connector-j', 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: 'com.h2database', name: 'h2', version: rootProject['verH2Conn']
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-slf4j2-impl', version: rootProject['verLog4j']
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-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']
bundle 'io.micrometer:micrometer-core:1.14.4' hikari 'io.micrometer:micrometer-core:1.0.6'
bundle('com.zaxxer:HikariCP:6.2.1') { hikari('hikari-cp:hikari-cp:2.6.0') {
exclude group: 'javassist' exclude group: 'javassist'
exclude group: 'io.micrometer' exclude group: 'io.micrometer'
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
compileOnly('net.sf.launch4j:launch4j:3.12') { // need user
exclude group: '*'
}
//compile 'org.mozilla:rhino:1.7.10' will be module
} }
tasks.register('dumpLibs', Copy) { task hikari(type: Copy) {
duplicatesStrategy = 'EXCLUDE' into "$buildDir/libs/libraries/hikaricp"
into "$buildDir/libs/libraries" from configurations.hikari
from configurations.bundleOnly
} }
tasks.register('dumpProguard', Copy) { task dumpLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE' dependsOn tasks.hikari
into "$buildDir/libs/proguard" into "$buildDir/libs/libraries"
from configurations.proguardPack from configurations.bundle
} }
tasks.register('bundle', Zip) { build.dependsOn tasks.dumpLibs
duplicatesStrategy = 'EXCLUDE'
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar
archiveFileName = 'LaunchServer.zip'
destinationDirectory = file("$buildDir")
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
from(tasks.jar)
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
}
tasks.register('dumpClientLibs', Copy) {
dependsOn parent.childProjects.Launcher.tasks.build
into "$buildDir/libs/launcher-libraries"
from parent.childProjects.Launcher.tasks.dumpLibs
}
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar, tasks.dumpProguard
publishing {
publications {
launchserverapi(MavenPublication) {
artifactId = 'launchserver-api'
artifact(cleanjar) {
classifier ""
}
artifact sourcesJar
artifact javadocJar
pom {
name = 'GravitLauncher LaunchServer API'
description = 'GravitLauncher LaunchServer Module API'
url = 'https://gravitlauncher.com'
licenses {
license {
name = 'GNU General Public License, Version 3.0'
url = 'https://www.gnu.org/licenses/gpl-3.0.html'
}
}
developers {
developer {
id = 'gravita'
name = 'Gravita'
email = 'gravita@gravit.pro'
}
developer {
id = 'zaxar163'
name = 'Zaxar163'
email = 'zahar.vcherachny@yandex.ru'
}
}
scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://gravitlauncher.com/'
}
}
}
}
}
signing {
sign publishing.publications.launchserverapi
}

View file

@ -1,106 +0,0 @@
package pro.gravit.launchserver;
import com.google.gson.JsonElement;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.helper.HttpHelper;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
public class HttpRequester {
private transient final HttpClient httpClient = HttpClient.newBuilder().build();
public HttpRequester() {
}
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz);
}
public <T> SimpleErrorHandler<T> makeEH(Type clazz) {
return new SimpleErrorHandler<>(clazz);
}
public <T> HttpRequest get(String url, String token) {
try {
var requestBuilder = HttpRequest.newBuilder()
.method("GET", HttpRequest.BodyPublishers.noBody())
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if (token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token));
}
return requestBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> HttpRequest post(String url, T request, String token) {
try {
var requestBuilder = HttpRequest.newBuilder()
.method("POST", HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request)))
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if (token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token));
}
return requestBuilder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Class<T> clazz) throws IOException {
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 String error;
public int code;
public SimpleError(String error) {
this.error = error;
}
@Override
public String toString() {
return "SimpleError{" +
"error='" + error + '\'' +
", code=" + code +
'}';
}
}
}

View file

@ -1,508 +0,0 @@
package pro.gravit.launchserver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.modules.events.ClosePhase;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.binary.EXELauncherBinary;
import pro.gravit.launchserver.binary.JARLauncherBinary;
import pro.gravit.launchserver.binary.LauncherBinary;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.launchserver.launchermodules.LauncherModuleLoader;
import pro.gravit.launchserver.manangers.*;
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
import pro.gravit.launchserver.modules.events.*;
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.response.auth.RestoreResponse;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.*;
import java.security.KeyStore;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The main LaunchServer class. Contains links to all necessary objects
* Not a singletron
*/
public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurable {
/**
* Working folder path
*/
public final Path dir;
/**
* Environment type (test / production)
*/
public final LaunchServerEnv env;
/**
* The path to the folder with libraries for the launcher
*/
public final Path launcherLibraries;
/**
* The path to the folder with compile-only libraries for the launcher
*/
public final Path launcherLibrariesCompile;
public final Path launcherPack;
/**
* The path to the folder with updates/webroot
*/
@Deprecated
public final Path updatesDir;
// Constant paths
/**
* Save/Reload LaunchServer config
*/
public final LaunchServerConfigManager launchServerConfigManager;
/**
* The path to the folder with profiles
*/
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
*/
public final LaunchServerRuntimeConfig runtime;
/**
* Pipeline for building JAR
*/
public final JARLauncherBinary launcherBinary;
/**
* Pipeline for building EXE
*/
public final LauncherBinary launcherEXEBinary;
// Server config
public final AuthHookManager authHookManager;
public final LaunchServerModulesManager modulesManager;
// Launcher binary
public final MirrorManager mirrorManager;
public final AuthManager authManager;
public final ReconfigurableManager reconfigurableManager;
public final ConfigManager configManager;
public final FeaturesManager featuresManager;
public final KeyAgreementManager keyAgreementManager;
public final UpdatesManager updatesManager;
// HWID ban + anti-brutforce
public final CertificateManager certificateManager;
// Server
public final CommandHandler commandHandler;
public final NettyServerSocketHandler nettyServerSocketHandler;
public final SocketCommandServer socketCommandServer;
public final ScheduledExecutorService service;
public final AtomicBoolean started = new AtomicBoolean(false);
public final LauncherModuleLoader launcherModuleLoader;
private final Logger logger = LogManager.getLogger();
public final int shardId;
public LaunchServerConfig config;
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, KeyAgreementManager keyAgreementManager, CommandHandler commandHandler, CertificateManager certificateManager, int shardId) throws IOException {
this.dir = directories.dir;
this.tmpDir = directories.tmpDir;
this.env = env;
this.config = config;
this.launchServerConfigManager = launchServerConfigManager;
this.modulesManager = modulesManager;
this.updatesDir = directories.updatesDir;
this.keyAgreementManager = keyAgreementManager;
this.commandHandler = commandHandler;
this.runtime = runtimeConfig;
this.certificateManager = certificateManager;
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir;
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);
modulesManager.invokeEvent(new NewLaunchServerInstanceEvent(this));
// Print keypair fingerprints
runtime.verify();
config.verify();
// build hooks, anti-brutforce and other
mirrorManager = new MirrorManager();
reconfigurableManager = new ReconfigurableManager();
authHookManager = new AuthHookManager();
configManager = new ConfigManager();
featuresManager = new FeaturesManager(this);
authManager = new AuthManager(this);
updatesManager = new UpdatesManager(this);
RestoreResponse.registerProviders(this);
config.init(ReloadType.FULL);
registerObject("launchServer", this);
pro.gravit.launchserver.command.handler.CommandHandler.registerCommands(commandHandler, this);
// init modules
modulesManager.invokeEvent(new LaunchServerInitPhase(this));
// Set launcher EXE binary
launcherBinary = new JARLauncherBinary(this);
launcherEXEBinary = binary();
launcherBinary.init();
launcherEXEBinary.init();
syncLauncherBinaries();
launcherModuleLoader = new LauncherModuleLoader(this);
if (config.components != null) {
logger.debug("Init components");
config.components.forEach((k, v) -> {
logger.debug("Init component {}", k);
v.setComponentName(k);
v.init(this);
});
logger.debug("Init components successful");
}
launcherModuleLoader.init();
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
modulesManager.invokeEvent(new LaunchServerPostInitPhase(this));
}
public void reload(ReloadType type) throws Exception {
config.close(type);
Map<String, AuthProviderPair> pairs = null;
if (type.equals(ReloadType.NO_AUTH)) {
pairs = config.auth;
}
logger.info("Reading LaunchServer config file");
config = launchServerConfigManager.readConfig();
config.setLaunchServer(this);
if (type.equals(ReloadType.NO_AUTH)) {
config.auth = pairs;
}
config.verify();
config.init(type);
if (type.equals(ReloadType.FULL) && config.components != null) {
logger.debug("Init components");
config.components.forEach((k, v) -> {
logger.debug("Init component {}", k);
v.setComponentName(k);
v.init(this);
});
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
public Map<String, Command> getCommands() {
Map<String, Command> commands = new HashMap<>();
SubCommand reload = new SubCommand("[type]", "reload launchserver config") {
@Override
public void invoke(String... args) throws Exception {
if (args.length == 0) {
reload(ReloadType.FULL);
return;
}
switch (args[0]) {
case "full" -> reload(ReloadType.FULL);
case "no_components" -> reload(ReloadType.NO_COMPONENTS);
default -> reload(ReloadType.NO_AUTH);
}
}
};
commands.put("reload", reload);
SubCommand save = new SubCommand("[]", "save launchserver config") {
@Override
public void invoke(String... args) throws Exception {
launchServerConfigManager.writeConfig(config);
launchServerConfigManager.writeRuntimeConfig(runtime);
logger.info("LaunchServerConfig saved");
}
};
commands.put("save", save);
LaunchServer instance = this;
SubCommand resetauth = new SubCommand("authId", "reset auth by id") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
AuthProviderPair pair = config.getAuthProviderPair(args[0]);
if (pair == null) {
logger.error("Pair not found");
return;
}
pair.core.close();
pair.core = new RejectAuthCoreProvider();
pair.core.init(instance, pair);
}
};
commands.put("resetauth", resetauth);
return commands;
}
public void checkCertificateExpired() {
if(!config.sign.enabled) {
return;
}
try {
KeyStore keyStore = SignHelper.getStore(Paths.get(config.sign.keyStore), config.sign.keyStorePass, config.sign.keyStoreType);
Instant date = SignHelper.getCertificateExpired(keyStore, config.sign.keyAlias);
if(date == null) {
logger.debug("The certificate will expire at unlimited");
} 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);
}
public void buildLauncherBinaries() throws IOException {
launcherBinary.build();
launcherEXEBinary.build();
}
public void close() throws Exception {
service.shutdownNow();
logger.info("Close server socket");
nettyServerSocketHandler.close();
// Close handlers & providers
config.close(ReloadType.FULL);
modulesManager.invokeEvent(new ClosePhase());
logger.info("Save LaunchServer runtime config");
launchServerConfigManager.writeRuntimeConfig(runtime);
// Print last message before death :(
logger.info("LaunchServer stopped");
}
@Deprecated
public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles();
}
@Deprecated
public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException();
}
public void rebindNettyServerSocket() {
nettyServerSocketHandler.close();
CommonHelper.newThread("Netty Server Socket Thread", false, nettyServerSocketHandler).start();
}
@Override
public void run() {
if (started.getAndSet(true))
throw new IllegalStateException("LaunchServer has been already started");
// Add shutdown hook, then start LaunchServer
if (!this.env.equals(LaunchServerEnv.TEST)) {
JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, () -> {
try {
close();
} catch (Exception e) {
logger.error("LaunchServer close error", e);
}
}));
CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
// Sync updates dir
CommonHelper.newThread("Profiles and updates sync", true, () -> {
try {
// Sync profiles dir
syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) {
logger.error("Updates/Profiles not synced", e);
}
}).start();
}
if (config.netty != null)
rebindNettyServerSocket();
try {
modulesManager.fullInitializedLaunchServer(this);
modulesManager.invokeEvent(new LaunchServerFullInitEvent(this));
logger.info("LaunchServer started");
} catch (Throwable e) {
logger.error("LaunchServer startup failed", e);
JVMHelper.RUNTIME.exit(-1);
}
}
public void syncLauncherBinaries() throws IOException {
logger.info("Syncing launcher binaries");
// Syncing launcher binary
logger.info("Syncing launcher binary file");
if (!launcherBinary.sync()) logger.warn("Missing launcher binary file");
// Syncing launcher EXE binary
logger.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync())
logger.warn("Missing launcher EXE binary file");
}
public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir");
config.profileProvider.sync();
if (config.netty.sendProfileUpdatesEvent) {
sendUpdateProfilesEvent();
}
}
private void sendUpdateProfilesEvent() {
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
return;
}
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 {
updatesManager.syncUpdatesDir(dirs);
}
public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);
}
}
public void unregisterObject(String name, Object object) {
if (object instanceof Reconfigurable) {
reconfigurableManager.unregisterReconfigurable(name);
}
}
public enum ReloadType {
NO_AUTH,
NO_COMPONENTS,
FULL
}
public enum LaunchServerEnv {
TEST,
DEV,
DEBUG,
PRODUCTION
}
public interface LaunchServerConfigManager {
LaunchServerConfig readConfig() throws IOException;
LaunchServerRuntimeConfig readRuntimeConfig() throws IOException;
void writeConfig(LaunchServerConfig config) throws IOException;
void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException;
}
public static class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack",
KEY_NAME = ".keys", MODULES = "modules", LAUNCHER_MODULES = "launcher-modules",
LIBRARIES = "libraries", CONTROL_FILE = "control-file", PROGUARD_DIR = "proguard-libraries";
public Path updatesDir;
public Path librariesDir;
public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
public Path keyDirectory;
public Path proguardDir;
public Path dir;
public Path trustStore;
public Path tmpDir;
public Path modules;
public Path launcherModules;
public Path controlFile;
public void collect() {
if (updatesDir == null) updatesDir = getPath(UPDATES_NAME);
if (trustStore == null) trustStore = getPath(TRUSTSTORE_NAME);
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if (launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_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)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("launchserver-%s".formatted(SecurityHelper.randomStringToken()));
}
private Path getPath(String dirName) {
String property = System.getProperty("launchserver.dir." + dirName, null);
if (property == null) return dir.resolve(dirName);
else return Paths.get(property);
}
}
}

View file

@ -1,113 +0,0 @@
package pro.gravit.launchserver;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.KeyAgreementManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.utils.command.CommandHandler;
import java.nio.file.Path;
public class LaunchServerBuilder {
private LaunchServerConfig config;
private LaunchServerRuntimeConfig runtimeConfig;
private CommandHandler commandHandler;
private LaunchServer.LaunchServerEnv env;
private LaunchServerModulesManager modulesManager;
private LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
private KeyAgreementManager keyAgreementManager;
private CertificateManager certificateManager;
private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
private Integer shardId;
public LaunchServerBuilder setConfig(LaunchServerConfig config) {
this.config = config;
return this;
}
public LaunchServerBuilder setEnv(LaunchServer.LaunchServerEnv env) {
this.env = env;
return this;
}
public LaunchServerBuilder setModulesManager(LaunchServerModulesManager modulesManager) {
this.modulesManager = modulesManager;
return this;
}
public LaunchServerBuilder setRuntimeConfig(LaunchServerRuntimeConfig runtimeConfig) {
this.runtimeConfig = runtimeConfig;
return this;
}
public LaunchServerBuilder setCommandHandler(CommandHandler commandHandler) {
this.commandHandler = commandHandler;
return this;
}
public LaunchServerBuilder setDirectories(LaunchServer.LaunchServerDirectories directories) {
this.directories = directories;
return this;
}
public LaunchServerBuilder setDir(Path dir) {
this.directories.dir = dir;
return this;
}
public LaunchServerBuilder setShardId(Integer shardId) {
this.shardId = shardId;
return this;
}
public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServerConfigManager launchServerConfigManager) {
this.launchServerConfigManager = launchServerConfigManager;
return this;
}
public LaunchServer build() throws Exception {
directories.collect();
if (launchServerConfigManager == null) {
launchServerConfigManager = new NullLaunchServerConfigManager();
}
if (keyAgreementManager == null) {
keyAgreementManager = new KeyAgreementManager(directories.keyDirectory);
}
if(shardId == null) {
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) {
this.certificateManager = certificateManager;
return this;
}
public void setKeyAgreementManager(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

@ -1,330 +0,0 @@
package pro.gravit.launchserver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.LauncherTrustManager;
import pro.gravit.launcher.base.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
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.profiles.ProfileProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.WebSocketService;
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.JLineCommandHandler;
import pro.gravit.utils.command.StdCommandHandler;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
import java.security.cert.CertificateException;
import java.util.List;
public class LaunchServerStarter {
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
public static final boolean prepareMode = Boolean.getBoolean("launchserver.prepareMode");
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) throws Exception {
JVMHelper.verifySystemProperties(LaunchServer.class, false);
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer");
Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile;
try {
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
Security.addProvider(new BouncyCastleProvider());
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
return;
}
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
directories.collect();
CertificateManager certificateManager = new CertificateManager();
try {
certificateManager.readTrustStore(directories.trustStore);
} catch (CertificateException e) {
throw new IOException(e);
}
{
LauncherTrustManager.CheckClassResult result = certificateManager.checkClass(LaunchServer.class);
if (result.type == LauncherTrustManager.CheckClassResultType.SUCCESS) {
logger.info("LaunchServer signed by {}", result.endCertificate.getSubjectX500Principal().getName());
} else if (result.type == LauncherTrustManager.CheckClassResultType.NOT_SIGNED) {
// None
} else {
if (result.exception != null) {
logger.error(result.exception);
}
logger.warn("LaunchServer signed incorrectly. Status: {}", result.type.name());
}
}
LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload();
modulesManager.initModules(null);
registerAll();
initGson(modulesManager);
printExperimentalBranch();
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
configFile = dir.resolve("LaunchServer.conf");
} else {
configFile = dir.resolve("LaunchServer.json");
}
if (IOHelper.exists(dir.resolve("RuntimeLaunchServer.conf"))) {
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.conf");
} else {
runtimeConfigFile = dir.resolve("RuntimeLaunchServer.json");
}
CommandHandler localCommandHandler;
try {
Class.forName("org.jline.terminal.Terminal");
// JLine2 available
localCommandHandler = new JLineCommandHandler();
logger.info("JLine2 terminal enabled");
} catch (ClassNotFoundException ignored) {
localCommandHandler = new StdCommandHandler(true);
logger.warn("JLine2 isn't in classpath, using std");
}
modulesManager.invokeEvent(new PreConfigPhase());
generateConfigIfNotExists(configFile, localCommandHandler, env);
logger.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
}
if (!Files.exists(runtimeConfigFile)) {
logger.info("Reset LaunchServer runtime config file");
runtimeConfig = new LaunchServerRuntimeConfig();
runtimeConfig.reset();
} else {
logger.info("Reading LaunchServer runtime config file");
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
runtimeConfig = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
}
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile);
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)
.setEnv(env)
.setCommandHandler(localCommandHandler)
.setRuntimeConfig(runtimeConfig)
.setConfig(config)
.setModulesManager(modulesManager)
.setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager)
.build();
List<String> allArgs = List.of(args);
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();
} else {
server.close();
}
}
public static void initGson(LaunchServerModulesManager modulesManager) {
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
Launcher.gsonManager.initGson();
}
public static void registerAll() {
AuthCoreProvider.registerProviders();
PasswordVerifier.registerProviders();
TextureProvider.registerProviders();
Component.registerComponents();
ProtectHandler.registerHandlers();
WebSocketService.registerResponses();
AuthRequest.registerProviders();
GetAvailabilityAuthRequest.registerProviders();
OptionalAction.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 {
if (IOHelper.isFile(configFile))
return;
// Create new config
logger.info("Creating LaunchServer config");
LaunchServerConfig newConfig = LaunchServerConfig.getDefault(env);
// Set server address
String address;
if (env.equals(LaunchServer.LaunchServerEnv.TEST)) {
address = "localhost";
newConfig.setProjectName("test");
} else {
address = System.getenv("ADDRESS");
if (address == null) {
address = System.getProperty("launchserver.address", null);
}
if (address == null) {
System.out.println("External launchServer address:port (default: localhost:9274): ");
address = commandHandler.readLine();
}
String projectName = System.getenv("PROJECTNAME");
if (projectName == null) {
projectName = System.getProperty("launchserver.projectname", null);
}
if (projectName == null) {
System.out.println("LaunchServer projectName: ");
projectName = commandHandler.readLine();
}
newConfig.setProjectName(projectName);
}
if (address == null || address.isEmpty()) {
logger.error("Address null. Using localhost:9274");
address = "localhost:9274";
}
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
logger.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft";
}
int port = 9274;
if(address.contains(":")) {
String portString = address.substring(address.indexOf(':')+1);
try {
port = Integer.parseInt(portString);
} 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
logger.info("Writing LaunchServer config file");
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
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,23 +0,0 @@
package pro.gravit.launchserver;
import pro.gravit.utils.command.Command;
import java.util.HashMap;
import java.util.Map;
/**
* Allows calling commands using the config command
*/
public interface Reconfigurable {
/**
* Gets a list of commands available for this object.
*
* @return Key - Command Name
* Value is a command object
*/
Map<String, Command> getCommands();
default Map<String, Command> defaultCommandsMap() {
return new HashMap<>();
}
}

View file

@ -1,19 +0,0 @@
package pro.gravit.launchserver;
import java.lang.instrument.Instrumentation;
import java.nio.file.*;
public final class StarterAgent {
public static Instrumentation inst = null;
public static Path libraries = null;
private static boolean isStarted = false;
public static boolean isAgentStarted() {
return isStarted;
}
public static void premain(String agentArgument, Instrumentation inst) {
throw new UnsupportedOperationException("Please remove -javaagent option from start.sh");
}
}

View file

@ -1,168 +0,0 @@
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.ClassVisitor;
import org.objectweb.asm.Opcodes;
import pro.gravit.utils.helper.IOHelper;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.jar.JarFile;
/**
* Позволяет искать методы внутри незагруженных классов и общие суперклассы для
* чего угодно. Работает через поиск class-файлов в classpath.
*/
public class ClassMetadataReader implements Closeable {
private final Logger logger = LogManager.getLogger(ClassMetadataReader.class);
private final List<JarFile> cp;
private final Map<String, Module> moduleClassFinder;
public ClassMetadataReader(List<JarFile> 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() {
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() {
return cp;
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
new ClassReader(classData).accept(visitor, 0);
}
public void acceptVisitor(String className, ClassVisitor visitor) throws IOException {
acceptVisitor(getClassData(className), visitor);
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor, int flags) {
new ClassReader(classData).accept(visitor, flags);
}
public void acceptVisitor(String className, ClassVisitor visitor, int flags) throws IOException {
acceptVisitor(getClassData(className), visitor, flags);
}
public byte[] getClassData(String className) throws IOException {
for (JarFile f : cp) {
if (f.getEntry(className + ".class") != null) {
byte[] bytes;
try (InputStream in = f.getInputStream(f.getEntry(className + ".class"))) {
bytes = IOHelper.read(in);
}
return bytes;
}
}
if(ClassMetadataReader.class.getModule().isNamed()) {
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) {
if (type.equals("java/lang/Object")) return null;
try {
return getSuperClassASM(type);
} catch (Exception e) {
logger.warn("getSuperClass: type {} not found ({}: {})", type, e.getClass().getName(), e.getMessage());
return "java/lang/Object";
}
}
protected String getSuperClassASM(String type) throws IOException {
CheckSuperClassVisitor cv = new CheckSuperClassVisitor();
acceptVisitor(type, cv);
return cv.superClassName;
}
/**
* Возвращает суперклассы в порядке возрастающей конкретности (начиная с
* java/lang/Object и заканчивая данным типом)
*/
public ArrayList<String> getSuperClasses(String type) {
ArrayList<String> superclasses = new ArrayList<>(1);
superclasses.add(type);
while ((type = getSuperClass(type)) != null)
superclasses.add(type);
Collections.reverse(superclasses);
return superclasses;
}
@Override
public void close() {
cp.forEach(IOHelper::close);
cp.clear();
}
private static class CheckSuperClassVisitor extends ClassVisitor {
String superClassName;
public CheckSuperClassVisitor() {
super(Opcodes.ASM9);
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
superClassName = superName;
}
}
}

View file

@ -1,254 +0,0 @@
package pro.gravit.launchserver.asm;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import pro.gravit.launcher.core.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor;
import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@SuppressWarnings("rawtypes")
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
private static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class,
java.lang.Float.class, java.lang.Double.class, java.lang.String.class);
private static final String INJECTED_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.class);
private static final List<String> primitiveLDCDescriptors = Arrays.asList(Type.INT_TYPE.getDescriptor(), Type.DOUBLE_TYPE.getDescriptor(),
Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
private static final Map<Class<?>, Serializer<?>> serializers;
static {
serializers = new HashMap<>();
serializers.put(List.class, new ListSerializer());
serializers.put(Map.class, new MapSerializer());
serializers.put(byte[].class, new ByteArraySerializer());
serializers.put(Short.class, serializerClass(Opcodes.I2S));
serializers.put(Byte.class, serializerClass(Opcodes.I2B));
serializers.put(Type.class, (Serializer<Type>) e -> { // ow.Type == java.lang.Class in LDC
InsnList ret = new InsnList();
ret.add(new LdcInsnNode(e));
return ret;
});
serializers.put(Boolean.class, (Serializer<Boolean>) e -> {
InsnList ret = new InsnList();
ret.add(new InsnNode(e ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
return ret;
});
serializers.put(Character.class, (Serializer<Character>) e -> {
InsnList ret = new InsnList();
ret.add(NodeUtils.push((int) e));
ret.add(new InsnNode(Opcodes.I2C));
return ret;
});
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter);
}
private final Map<String, Object> values;
public InjectClassAcceptor(Map<String, Object> values) {
this.values = values;
}
private static void visit(ClassNode classNode, Map<String, Object> values) {
MethodNode clinitMethod = classNode.methods.stream().filter(methodNode -> "<clinit>".equals(methodNode.name))
.findFirst().orElseGet(() -> {
MethodNode newClinitMethod = new MethodNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
"<clinit>", "()V", null, null);
newClinitMethod.instructions.add(new InsnNode(Opcodes.RETURN));
classNode.methods.add(newClinitMethod);
return newClinitMethod;
});
List<MethodNode> constructors = classNode.methods.stream().filter(method -> "<init>".equals(method.name))
.toList();
MethodNode initMethod = constructors.stream().filter(method -> method.invisibleAnnotations != null
&& method.invisibleAnnotations.stream().anyMatch(annotation -> INJECTED_CONSTRUCTOR_DESC.equals(annotation.desc))).findFirst()
.orElseGet(() -> constructors.stream().filter(method -> method.desc.equals("()V")).findFirst().orElse(null));
classNode.fields.forEach(field -> {
// Notice that fields that will be used with this algo should not have default
// value by = ...;
boolean isStatic = (field.access & Opcodes.ACC_STATIC) != 0;
injectTo(isStatic ? clinitMethod : initMethod, classNode, field, isStatic, values);
});
}
public static void injectTo(MethodNode initMethod, ClassNode classNode, FieldNode field, boolean isStatic, Map<String, Object> values) {
AnnotationNode valueAnnotation = field.invisibleAnnotations != null ? field.invisibleAnnotations.stream()
.filter(annotation -> INJECTED_FIELD_DESC.equals(annotation.desc)).findFirst()
.orElse(null) : null;
if (valueAnnotation == null) {
return;
}
field.invisibleAnnotations.remove(valueAnnotation);
AtomicReference<String> valueName = new AtomicReference<>(null);
valueAnnotation.accept(new AnnotationVisitor(Opcodes.ASM7) {
@Override
public void visit(final String name, final Object value) {
if ("value".equals(name)) {
if (value.getClass() != String.class)
throw new IllegalArgumentException(
"Invalid annotation with value class %s".formatted(field.getClass().getName()));
valueName.set(value.toString());
}
}
});
if (valueName.get() == null) {
throw new IllegalArgumentException("Annotation should always contains 'value' key");
}
if (!values.containsKey(valueName.get())) {
return;
}
Object value = values.get(valueName.get());
//if ((field.access & Opcodes.ACC_STATIC) != 0) {
if (isStatic) {
if (primitiveLDCDescriptors.contains(field.desc) && primitiveLDCClasses.contains(value.getClass())) {
field.value = value;
return;
}
List<FieldInsnNode> putStaticNodes = Arrays.stream(initMethod.instructions.toArray())
.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();
InsnList setter = serializeValue(value);
if (putStaticNodes.isEmpty()) {
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
Arrays.stream(initMethod.instructions.toArray()).filter(node -> node.getOpcode() == Opcodes.RETURN)
.forEach(node -> initMethod.instructions.insertBefore(node, setter));
} else {
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
for (FieldInsnNode fieldInsnNode : putStaticNodes) {
initMethod.instructions.insertBefore(fieldInsnNode, setter);
}
}
} else {
if (initMethod == null) {
throw new IllegalArgumentException("Not found init in target: %s".formatted(classNode.name));
}
List<FieldInsnNode> putFieldNodes = Arrays.stream(initMethod.instructions.toArray())
.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();
InsnList setter = serializeValue(value);
if (putFieldNodes.isEmpty()) {
setter.insert(new VarInsnNode(Opcodes.ALOAD, 0));
setter.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, field.name, field.desc));
Arrays.stream(initMethod.instructions.toArray())
.filter(node -> node.getOpcode() == Opcodes.RETURN)
.forEach(node -> initMethod.instructions.insertBefore(node, setter));
} else {
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
for (FieldInsnNode fieldInsnNode : putFieldNodes) {
initMethod.instructions.insertBefore(fieldInsnNode, setter);
}
}
}
}
private static Serializer<?> serializerClass(int opcode) {
return (Serializer<Number>) value -> {
InsnList ret = new InsnList();
ret.add(NodeUtils.push(value.intValue()));
ret.add(new InsnNode(opcode));
return ret;
};
}
@SuppressWarnings("unchecked")
private static InsnList serializeValue(Object value) {
if (value == null) {
InsnList insnList = new InsnList();
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
return insnList;
}
if (primitiveLDCClasses.contains(value.getClass())) {
InsnList insnList = new InsnList();
insnList.add(new LdcInsnNode(value));
return insnList;
}
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
if (serializerEntry.getKey().isInstance(value)) {
return ((Serializer) serializerEntry.getValue()).serialize(value);
}
}
throw new UnsupportedOperationException("Serialization of type %s is not supported".formatted(value.getClass()));
}
public static boolean isSerializableValue(Object value) {
if (value == null) return true;
if (primitiveLDCClasses.contains(value.getClass())) return true;
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
if (serializerEntry.getKey().isInstance(value)) {
return true;
}
}
return false;
}
@Override
public void transform(ClassNode classNode, String className, BuildContext context) {
visit(classNode, values);
}
@FunctionalInterface
private interface Serializer<T> {
InsnList serialize(T value);
}
private static class ListSerializer implements Serializer<List> {
@Override
public InsnList serialize(List value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(NodeUtils.push(value.size()));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
for (Object object : value) {
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(object));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
private static class MapSerializer implements Serializer<Map> {
@Override
public InsnList serialize(Map value) {
InsnList insnList = new InsnList();
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE), false));
for (Object entryObject : value.entrySet()) {
Map.Entry entry = (Map.Entry) entryObject;
insnList.add(new InsnNode(Opcodes.DUP));
insnList.add(serializeValue(entry.getKey()));
insnList.add(serializeValue(entry.getValue()));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "put",
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)),
true));
insnList.add(new InsnNode(Opcodes.POP));
}
return insnList;
}
}
private static class ByteArraySerializer implements Serializer<byte[]> {
@Override
public InsnList serialize(byte[] value) {
InsnList insnList = new InsnList();
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
false));
return insnList;
}
}
}

View file

@ -1,243 +0,0 @@
package pro.gravit.launchserver.asm;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JarHelper;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.objectweb.asm.Opcodes.*;
public final class NodeUtils {
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
private NodeUtils() {
}
public static ClassNode forClass(Class<?> cls, int flags) {
try (InputStream in = JarHelper.getClassBytesStream(cls)) {
ClassNode ret = new ClassNode();
new ClassReader(IOHelper.read(in)).accept(ret, flags);
return ret;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static ClassNode forClass(String clazz, int flags, ClassMetadataReader r) {
try {
ClassNode ret = new ClassNode();
r.acceptVisitor(clazz, ret, flags);
return ret;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static List<AnnotationNode> annots(String clazz, String method, ClassMetadataReader r) {
if (clazz.startsWith("L")) clazz = Type.getType(clazz).getInternalName();
try {
List<AnnotationNode> ret = new ArrayList<>();
ClassNode n = forClass(clazz, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG, r);
if (n.visibleAnnotations != null) ret.addAll(n.visibleAnnotations);
if (n.invisibleAnnotations != null) ret.addAll(n.invisibleAnnotations);
for (MethodNode m : n.methods)
if (method.equals(m.name)) {
if (m.visibleAnnotations != null) ret.addAll(m.visibleAnnotations);
if (m.invisibleAnnotations != null) ret.addAll(m.invisibleAnnotations);
}
return ret;
} catch (Throwable e) {
return Collections.emptyList();
}
}
private static int doMethodEmulation(String desc) {
int result = 0;
Type returnType = Type.getReturnType(desc);
if (returnType.getSort() == Type.LONG || returnType.getSort() == Type.DOUBLE)
result++;
if (returnType.getSort() != Type.VOID)
result++;
return result;
}
public static int opcodeEmulation(AbstractInsnNode e) {
int stackSize = 0;
switch (e.getOpcode()) {
case NOP:
case LALOAD: // (index, arrayref) -> (long, long_top)
case DALOAD: // (index, arrayref) -> (double, double_top)
case SWAP: // (value1, value2) -> (value2, value1)
case INEG:
case LNEG:
case FNEG:
case DNEG:
case IINC:
case I2F:
case L2D:
case F2I:
case D2L:
case I2B:
case I2C:
case I2S:
case GOTO:
case RETURN:
case NEWARRAY:
case ANEWARRAY:
case ARRAYLENGTH:
case CHECKCAST:
case INSTANCEOF:
// Does nothing
break;
case ACONST_NULL:
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
case FCONST_0:
case FCONST_1:
case FCONST_2:
case BIPUSH:
case SIPUSH:
case ILOAD:
case FLOAD:
case ALOAD:
case DUP:
case DUP_X1:
case DUP_X2:
case I2L:
case I2D:
case F2L:
case F2D:
case NEW:
// Pushes one-word constant to stack
stackSize++;
break;
case LDC:
LdcInsnNode ldc = (LdcInsnNode) e;
if (ldc.cst instanceof Long || ldc.cst instanceof Double)
stackSize++;
stackSize++;
break;
case LCONST_0:
case LCONST_1:
case DCONST_0:
case DCONST_1:
case LLOAD:
case DLOAD:
case DUP2:
case DUP2_X1:
case DUP2_X2:
// Pushes two-word constant or two one-word constants to stack
stackSize++;
stackSize++;
break;
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKEINTERFACE, INVOKESTATIC:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKEDYNAMIC:
stackSize += doMethodEmulation(((InvokeDynamicInsnNode) e).desc);
break;
case JSR:
case RET:
throw new RuntimeException("Did not expect JSR/RET instructions");
default:
break;
}
return stackSize;
}
public static InsnList getSafeStringInsnList(String string) {
InsnList insnList = new InsnList();
if ((string.length() * 3) < MAX_SAFE_BYTE_COUNT) { // faster check
insnList.add(new LdcInsnNode(string));
return insnList;
}
insnList.add(new TypeInsnNode(NEW, "java/lang/StringBuilder"));
insnList.add(new InsnNode(DUP));
insnList.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false));
String[] chunks = splitUtf8ToChunks(string, MAX_SAFE_BYTE_COUNT);
for (String chunk : chunks) {
insnList.add(new LdcInsnNode(chunk));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
}
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false));
return insnList;
}
public static String[] splitUtf8ToChunks(String text, int maxBytes) {
List<String> parts = new ArrayList<>();
char[] chars = text.toCharArray();
int lastCharIndex = 0;
int currentChunkSize = 0;
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
int charSize = getUtf8CharSize(c);
if (currentChunkSize + charSize < maxBytes) {
currentChunkSize += charSize;
} else {
parts.add(text.substring(lastCharIndex, i));
currentChunkSize = 0;
lastCharIndex = i;
}
}
if (currentChunkSize != 0) {
parts.add(text.substring(lastCharIndex));
}
return parts.toArray(new String[0]);
}
public static int getUtf8CharSize(char c) {
if (c >= 0x0001 && c <= 0x007F) {
return 1;
} else if (c <= 0x07FF) {
return 2;
}
return 3;
}
public static InsnList push(final int value) {
InsnList ret = new InsnList();
if (value >= -1 && value <= 5)
ret.add(new InsnNode(Opcodes.ICONST_0 + value));
else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)
ret.add(new IntInsnNode(Opcodes.BIPUSH, value));
else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
ret.add(new IntInsnNode(Opcodes.SIPUSH, value));
else
ret.add(new LdcInsnNode(value));
return ret;
}
public static InsnList makeValueEnumGetter(@SuppressWarnings("rawtypes") Enum u) {
InsnList ret = new InsnList();
Type e = Type.getType(u.getClass());
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
return ret;
}
}

View file

@ -1,41 +0,0 @@
package pro.gravit.launchserver.asm;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.util.ArrayList;
/**
* ClassWriter с другой реализацией метода getCommonSuperClass: при его
* использовании не происходит загрузки классов.
*/
public class SafeClassWriter extends ClassWriter {
private final ClassMetadataReader classMetadataReader;
public SafeClassWriter(ClassMetadataReader classMetadataReader, int flags) {
super(flags);
this.classMetadataReader = classMetadataReader;
}
public SafeClassWriter(ClassReader classReader, ClassMetadataReader classMetadataReader, int flags) {
super(classReader, flags);
this.classMetadataReader = classMetadataReader;
}
@Override
protected String getCommonSuperClass(String type1, String type2) {
ArrayList<String> superClasses1 = classMetadataReader.getSuperClasses(type1);
ArrayList<String> superClasses2 = classMetadataReader.getSuperClasses(type2);
int size = Math.min(superClasses1.size(), superClasses2.size());
int i;
for (i = 0; i < size && superClasses1.get(i).equals(superClasses2.get(i)); i++)
;
if (i == 0)
return "java/lang/Object";
else
return superClasses1.get(i - 1);
}
}

View file

@ -1,45 +0,0 @@
package pro.gravit.launchserver.auth;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import java.io.IOException;
import java.io.Serial;
import java.util.List;
import java.util.stream.Collectors;
public final class AuthException extends IOException {
@Serial
private static final long serialVersionUID = -2586107832847245863L;
public AuthException(String message) {
super(message);
}
public AuthException(String message, Throwable cause) {
super(message, cause);
}
public static AuthException need2FA() {
return new AuthException(AuthRequestEvent.TWO_FACTOR_NEED_ERROR_MESSAGE);
}
public static AuthException needMFA(List<Integer> factors) {
String message = AuthRequestEvent.ONE_FACTOR_NEED_ERROR_MESSAGE_PREFIX
.concat(factors.stream().map(String::valueOf).collect(Collectors.joining(".")));
return new AuthException(message);
}
public static AuthException wrongPassword() {
return new AuthException(AuthRequestEvent.WRONG_PASSWORD_ERROR_MESSAGE);
}
public static AuthException userNotFound() {
return new AuthException(AuthRequestEvent.USER_NOT_FOUND_ERROR_MESSAGE);
}
@Override
public String toString() {
return getMessage();
}
}

View file

@ -1,113 +0,0 @@
package pro.gravit.launchserver.auth;
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.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public final class AuthProviderPair {
private transient final Logger logger = LogManager.getLogger();
public boolean isDefault = true;
public AuthCoreProvider core;
public TextureProvider textureProvider;
public Map<String, MixProvider> mixes;
public Map<String, String> links;
public transient String name;
public transient Set<String> features;
public String displayName;
public boolean visible = true;
public AuthProviderPair() {
}
public AuthProviderPair(AuthCoreProvider core, TextureProvider textureProvider) {
this.core = core;
this.textureProvider = textureProvider;
}
public static Set<String> getFeatures(Class<?> clazz) {
Set<String> list = new HashSet<>();
getFeatures(clazz, list);
return list;
}
public Set<String> getFeatures() {
return features;
}
public static void getFeatures(Class<?> clazz, Set<String> list) {
Feature[] features = clazz.getAnnotationsByType(Feature.class);
for (Feature feature : features) {
list.add(feature.value());
}
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && superClass != Object.class) {
getFeatures(superClass, list);
}
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
getFeatures(i, list);
}
}
public <T> T isSupport(Class<T> clazz) {
if (core == null) return null;
T result = core.isSupport(clazz);
if (result == null && mixes != null) {
for(var m : mixes.values()) {
result = m.isSupport(clazz);
if(result != null) {
break;
}
}
}
return result;
}
public void init(LaunchServer srv, String name) {
this.name = name;
if (links != null) link(srv);
core.init(srv, this);
features = new HashSet<>();
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) {
links.forEach((k, v) -> {
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
if (pair == null) {
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v));
}
if ("core".equals(k)) {
if (pair.core == null)
throw new NullPointerException("Auth %s link failed. %s.core is null".formatted(name, v));
core = pair.core;
}
});
}
public void close() throws IOException {
core.close();
if (textureProvider != null) {
textureProvider.close();
}
if(mixes != null) {
for(var m : mixes.values()) {
m.close();
}
}
}
}

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Repeatable(Features.class)
public @interface Feature {
String value();
}

View file

@ -1,12 +0,0 @@
package pro.gravit.launchserver.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Features {
Feature[] value();
}

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

@ -1,127 +0,0 @@
package pro.gravit.launchserver.auth;
import com.mysql.cj.jdbc.MysqlDataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.VerifyHelper;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES;
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.mysql.idleTimeout can't be <= 5000");
private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))),
VerifyHelper.POSITIVE, "launcher.mysql.maxPoolSize can't be <= 0");
// Instance
private transient final String poolName;
private transient final Logger logger = LogManager.getLogger();
// Config
private String address;
private int port;
private boolean useSSL;
private boolean verifyCertificates;
private String username;
private String password;
private String database;
private String timezone;
private long hikariMaxLifetime = MINUTES.toMillis(30);
private boolean useHikari;
// Cache
private transient DataSource source;
private transient boolean hikari;
public MySQLSourceConfig(String poolName) {
this.poolName = poolName;
}
public MySQLSourceConfig(String poolName, String address, int port, String username, String password, String database) {
this.poolName = poolName;
this.address = address;
this.port = port;
this.username = username;
this.password = password;
this.database = database;
}
public MySQLSourceConfig(String poolName, DataSource source, boolean hikari) {
this.poolName = poolName;
this.source = source;
this.hikari = hikari;
}
@Override
public synchronized void close() {
if (hikari)
((HikariDataSource) source).close();
}
public synchronized Connection getConnection() throws SQLException {
if (source == null) { // New data source
MysqlDataSource mysqlSource = new MysqlDataSource();
mysqlSource.setCharacterEncoding("UTF-8");
// Prep statements cache
mysqlSource.setPrepStmtCacheSize(250);
mysqlSource.setPrepStmtCacheSqlLimit(2048);
mysqlSource.setCachePrepStmts(true);
mysqlSource.setUseServerPrepStmts(true);
// General optimizations
mysqlSource.setCacheServerConfiguration(true);
mysqlSource.setUseLocalSessionState(true);
mysqlSource.setRewriteBatchedStatements(true);
mysqlSource.setMaintainTimeStats(false);
mysqlSource.setUseUnbufferedInput(false);
mysqlSource.setUseReadAheadInput(false);
mysqlSource.setUseSSL(useSSL);
mysqlSource.setVerifyServerCertificate(verifyCertificates);
// Set credentials
mysqlSource.setServerName(address);
mysqlSource.setPortNumber(port);
mysqlSource.setUser(username);
mysqlSource.setPassword(password);
mysqlSource.setDatabaseName(database);
mysqlSource.setTcpNoDelay(true);
if (timezone != null) mysqlSource.setServerTimezone(timezone);
hikari = false;
// Try using HikariCP
source = mysqlSource;
if (useHikari) {
try {
Class.forName("com.zaxxer.hikari.HikariDataSource");
hikari = true; // Used for shutdown. Not instanceof because of possible classpath error
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDataSource(mysqlSource);
hikariConfig.setPoolName(poolName);
hikariConfig.setMinimumIdle(1);
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setConnectionTimeout(1000);
hikariConfig.setLeakDetectionThreshold(2000);
hikariConfig.setMaxLifetime(hikariMaxLifetime);
// Set HikariCP pool
// Replace source with hds
source = new HikariDataSource(hikariConfig);
} catch (ClassNotFoundException ignored) {
logger.debug("HikariCP isn't in classpath for '{}'", poolName);
}
}
}
return source.getConnection();
}
}

View file

@ -1,85 +0,0 @@
package pro.gravit.launchserver.auth;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.postgresql.ds.PGSimpleDataSource;
import pro.gravit.utils.helper.VerifyHelper;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.maxPoolSize", Integer.toString(3))),
VerifyHelper.POSITIVE, "launcher.postgresql.maxPoolSize can't be <= 0");
private transient final Logger logger = LogManager.getLogger();
// Instance
private String poolName;
// Config
private String[] addresses;
private int[] ports;
private String username;
private String password;
private String database;
private long hikariMaxLifetime = MINUTES.toMillis(30); // 30 minutes
// Cache
private transient DataSource source;
private transient boolean hikari;
@Override
public synchronized void close() {
if (hikari) { // Shutdown hikari pool
((HikariDataSource) source).close();
}
}
public synchronized Connection getConnection() throws SQLException {
if (source == null) { // New data source
PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
// Set credentials
postgresqlSource.setServerNames(addresses);
postgresqlSource.setPortNumbers(ports);
postgresqlSource.setUser(username);
postgresqlSource.setPassword(password);
postgresqlSource.setDatabaseName(database);
// Try using HikariCP
source = postgresqlSource;
//noinspection Duplicates
try {
Class.forName("com.zaxxer.hikari.HikariDataSource");
hikari = true; // Used for shutdown. Not instanceof because of possible classpath error
// Set HikariCP pool
HikariDataSource hikariSource = new HikariDataSource();
hikariSource.setDataSource(source);
// Set pool settings
hikariSource.setPoolName(poolName);
hikariSource.setMinimumIdle(0);
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariSource.setIdleTimeout(SECONDS.toMillis(TIMEOUT));
hikariSource.setMaxLifetime(hikariMaxLifetime);
// Replace source with hds
source = hikariSource;
logger.info("HikariCP pooling enabled for '{}'", poolName);
} catch (ClassNotFoundException ignored) {
logger.warn("HikariCP isn't in classpath for '{}'", poolName);
}
}
return source.getConnection();
}
}

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

@ -1,414 +0,0 @@
package pro.gravit.launchserver.auth.core;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.events.RequestEvent;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import pro.gravit.launcher.base.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launcher.base.profiles.PlayerProfile;
import pro.gravit.launcher.base.request.auth.AuthRequest;
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.Reconfigurable;
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.provider.AuthSupportGetAllUsers;
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.AuthSupportSudo;
import pro.gravit.launchserver.auth.core.openid.OpenIDAuthCoreProvider;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/*
All-In-One provider
*/
public abstract class AuthCoreProvider implements AutoCloseable, Reconfigurable {
public static final ProviderMap<AuthCoreProvider> providers = new ProviderMap<>("AuthCoreProvider");
private static final Logger logger = LogManager.getLogger();
private static boolean registredProviders = false;
protected transient LaunchServer server;
protected transient AuthProviderPair pair;
public static void registerProviders() {
if (!registredProviders) {
providers.register("reject", RejectAuthCoreProvider.class);
providers.register("mysql", MySQLCoreProvider.class);
providers.register("postgresql", PostgresSQLCoreProvider.class);
providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("merge", MergeAuthCoreProvider.class);
providers.register("openid", OpenIDAuthCoreProvider.class);
providers.register("sql", SQLCoreProvider.class);
registredProviders = true;
}
}
public abstract User getUserByUsername(String username);
public User getUserByLogin(String login) {
return getUserByUsername(login);
}
public abstract User getUserByUUID(UUID uuid);
public abstract UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired;
public abstract AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context /* may be null */);
public void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
// None
}
public abstract AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context /* may be null */, AuthRequest.AuthPasswordInterface password /* may be null */, boolean minecraftAccess) throws IOException;
public AuthManager.AuthReport authorize(User user, AuthResponse.AuthContext context /* may be null */, AuthRequest.AuthPasswordInterface password /* may be null */, boolean minecraftAccess) throws IOException {
return authorize(user.getUsername(), context, password, minecraftAccess);
}
public void init(LaunchServer server, AuthProviderPair pair) {
this.server = server;
this.pair = pair;
}
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return List.of(new AuthPasswordDetails());
}
@Override
public Map<String, Command> getCommands() {
Map<String, Command> map = defaultCommandsMap();
map.put("auth", new SubCommand("[login] (json/plain password data)", "Test auth") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
AuthRequest.AuthPasswordInterface password = null;
if (args.length > 1) {
if (args[1].startsWith("{")) {
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
} else {
password = new AuthPlainPassword(args[1]);
}
}
var report = authorize(args[0], null, password, false);
if (report.isUsingOAuth()) {
logger.info("OAuth: AccessToken: {} RefreshToken: {} MinecraftAccessToken: {}", report.oauthAccessToken(), report.oauthRefreshToken(), report.minecraftAccessToken());
if (report.session() != null) {
logger.info("UserSession: id {} expire {} user {}", report.session().getID(), report.session().getExpireIn(), report.session().getUser() == null ? "null" : "found");
logger.info(report.session().toString());
}
} else {
logger.info("Basic: MinecraftAccessToken: {}", report.minecraftAccessToken());
}
}
});
map.put("getuserbyusername", new SubCommand("[username]", "get user by username") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
User user = getUserByUsername(args[0]);
if (user == null) {
logger.info("User {} not found", args[0]);
} else {
logger.info("User {}: {}", args[0], user.toString());
}
}
});
map.put("getuserbyuuid", new SubCommand("[uuid]", "get user by uuid") {
@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]);
} else {
logger.info("User {}: {}", args[0], user.toString());
}
}
});
{
var instance = isSupport(AuthSupportGetAllUsers.class);
if (instance != null) {
map.put("getallusers", new SubCommand("(limit)", "print all users information") {
@Override
public void invoke(String... args) {
int max = Integer.MAX_VALUE;
if (args.length > 0) max = Integer.parseInt(args[0]);
Iterable<User> users = instance.getAllUsers();
int counter = 0;
for (User u : users) {
logger.info("User {}", u.toString());
counter++;
if (counter == max) break;
}
logger.info("Found {} users", counter);
}
});
}
}
{
var instance = isSupport(AuthSupportHardware.class);
if (instance != null) {
map.put("gethardwarebyid", new SubCommand("[id]", "get hardware by id") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
UserHardware hardware = instance.getHardwareInfoById(args[0]);
if (hardware == null) {
logger.info("UserHardware {} not found", args[0]);
} else {
logger.info("UserHardware: {}", hardware);
}
}
});
map.put("gethardwarebydata", new SubCommand("[json data]", "fulltext search hardware by json data(slow)") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
UserHardware hardware = instance.getHardwareInfoByData(Launcher.gsonManager.gson.fromJson(args[0], HardwareReportRequest.HardwareInfo.class));
if (hardware == null) {
logger.info("UserHardware {} not found", args[0]);
} else {
logger.info("UserHardware: {}", hardware);
}
}
});
map.put("findmulti", new SubCommand("[hardware id]", "get all users in one hardware id") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
UserHardware hardware = instance.getHardwareInfoById(args[0]);
if (hardware == null) {
logger.info("UserHardware {} not found", args[0]);
return;
}
Iterable<User> users = instance.getUsersByHardwareInfo(hardware);
for (User user : users) {
logger.info("User {}", user);
}
}
});
map.put("banhardware", new SubCommand("[hardware id]", "ban hardware by id") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
UserHardware hardware = instance.getHardwareInfoById(args[0]);
if (hardware == null) {
logger.info("UserHardware {} not found", args[0]);
return;
}
instance.banHardware(hardware);
logger.info("UserHardware {} banned", args[0]);
}
});
map.put("unbanhardware", new SubCommand("[hardware id]", "ban hardware by id") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
UserHardware hardware = instance.getHardwareInfoById(args[0]);
if (hardware == null) {
logger.info("UserHardware {} not found", args[0]);
return;
}
instance.unbanHardware(hardware);
logger.info("UserHardware {} unbanned", args[0]);
}
});
map.put("comparehardware", new SubCommand("[json data 1] [json data 2]", "compare hardware info") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
HardwareReportRequest.HardwareInfo hardware1 = Launcher.gsonManager.gson.fromJson(args[0], HardwareReportRequest.HardwareInfo.class);
HardwareReportRequest.HardwareInfo hardware2 = Launcher.gsonManager.gson.fromJson(args[1], HardwareReportRequest.HardwareInfo.class);
AuthSupportHardware.HardwareInfoCompareResult result = instance.compareHardwareInfo(hardware1, hardware2);
if (result == null) {
logger.error("Method compareHardwareInfo return null");
return;
}
logger.info("Compare result: {} Spoof: {} first {} second", result.compareLevel, result.firstSpoofingLevel, result.secondSpoofingLevel);
}
});
}
}
{
var instance = isSupport(AuthSupportRegistration.class);
if (instance != null) {
map.put("register", new SubCommand("[username] [email] [plain or json password] (json args)", "Register new user") {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
Map<String, String> map = null;
String username = args[0];
String email = args[1];
String plainPassword = args[2];
if (args.length > 3) {
Type typeOfMap = new TypeToken<Map<String, String>>() {
}.getType();
map = Launcher.gsonManager.gson.fromJson(args[2], typeOfMap);
}
AuthRequest.AuthPasswordInterface password;
if (plainPassword.startsWith("{")) {
password = Launcher.gsonManager.gson.fromJson(plainPassword, AuthRequest.AuthPasswordInterface.class);
} else {
password = new AuthPlainPassword(plainPassword);
}
User user = instance.registration(username, email, password, map);
logger.info("User '{}' registered", user.toString());
}
});
}
}
{
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;
}
public abstract User checkServer(Client client, String username, String serverID) throws IOException;
public abstract boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) throws IOException;
@SuppressWarnings("unchecked")
public <T> T isSupport(Class<T> clazz) {
if (clazz.isAssignableFrom(getClass())) return (T) this;
return null;
}
@Override
public abstract void close();
public static class PasswordVerifyReport {
public static final PasswordVerifyReport REQUIRED_2FA = new PasswordVerifyReport(-1);
public static final PasswordVerifyReport FAILED = new PasswordVerifyReport(false);
public static final PasswordVerifyReport OK = new PasswordVerifyReport(true);
public final boolean success;
public final boolean needMoreFactors;
public final List<Integer> factors;
public PasswordVerifyReport(boolean success) {
this.success = success;
this.needMoreFactors = false;
this.factors = List.of();
}
public PasswordVerifyReport(AuthManager.AuthReport report) {
this.success = true;
this.needMoreFactors = false;
this.factors = List.of();
}
public PasswordVerifyReport(int nextFactor) {
this.success = false;
this.needMoreFactors = true;
this.factors = List.of(nextFactor);
}
public PasswordVerifyReport(List<Integer> factors) {
this.success = false;
this.needMoreFactors = false;
this.factors = Collections.unmodifiableList(factors);
}
private PasswordVerifyReport(boolean success, boolean needMoreFactors, List<Integer> factors) {
this.success = success;
this.needMoreFactors = needMoreFactors;
this.factors = factors;
}
public boolean isSuccess() {
return success;
}
}
public static class OAuthAccessTokenExpired extends Exception {
public OAuthAccessTokenExpired() {
}
public OAuthAccessTokenExpired(String message) {
super(message);
}
public OAuthAccessTokenExpired(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -1,204 +0,0 @@
package pro.gravit.launchserver.auth.core;
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.details.AuthLoginOnlyDetails;
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.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class MemoryAuthCoreProvider extends AuthCoreProvider implements AuthSupportSudo {
private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override
public User getUserByUsername(String username) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.username.equals(username)) {
return u;
}
}
var result = new MemoryUser(username);
memory.add(result);
return result;
}
}
@Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
return List.of(new AuthLoginOnlyDetails());
}
@Override
public User getUserByUUID(UUID uuid) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.uuid.equals(uuid)) {
return u;
}
}
}
return null;
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.accessToken.equals(accessToken)) {
return new MemoryUserSession(u);
}
}
}
return null;
}
@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 {
if (login == null) {
throw AuthException.userNotFound();
}
MemoryUser user = null;
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.username.equals(login)) {
user = u;
break;
}
}
if (user == null) {
user = new MemoryUser(login);
memory.add(user);
}
}
if (!minecraftAccess) {
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
} else {
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
}
}
@Override
public User checkServer(Client client, String username, String serverID) {
synchronized (memory) {
for (MemoryUser u : memory) {
if (u.username.equals(username)) {
return u;
}
}
var result = new MemoryUser(username);
memory.add(result);
return result;
}
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return true;
}
@Override
public void close() {
}
@Override
public AuthManager.AuthReport sudo(User user, boolean shadow) throws IOException {
return authorize(user.getUsername(), null, null, true);
}
public static class MemoryUser implements User {
private final String username;
private final UUID uuid;
private String serverId;
private final String accessToken;
private final ClientPermissions permissions;
public MemoryUser(String username) {
this.username = username;
this.uuid = makeUuidFromUsername(username);
this.accessToken = SecurityHelper.randomStringToken();
this.permissions = new ClientPermissions();
}
private static UUID makeUuidFromUsername(String username) {
return UUID.nameUUIDFromBytes(username.getBytes(StandardCharsets.UTF_8));
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemoryUser that = (MemoryUser) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
}
public static class MemoryUserSession implements UserSession {
private final String id;
private final MemoryUser user;
private final long expireIn;
public MemoryUserSession(MemoryUser user) {
this.id = SecurityHelper.randomStringToken();
this.user = user;
this.expireIn = 0;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public String getMinecraftAccessToken() {
return "IGNORED";
}
@Override
public long getExpireIn() {
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,354 +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.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware;
import pro.gravit.utils.helper.IOHelper;
import java.io.ByteArrayInputStream;
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 MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware {
public MySQLSourceConfig mySQLHolder;
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 SQLSourceConfig getSQLConfig() {
return mySQLHolder;
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'mysql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
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 MySQLUser constructUser(ResultSet set) throws SQLException {
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;
}
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo();
hardwareInfo.hwDiskId = set.getString("hwDiskId");
hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber");
Blob displayId = set.getBlob("displayId");
hardwareInfo.displayId = displayId == null ? null : IOHelper.read(displayId.getBinaryStream());
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");
Blob publicKey = set.getBlob("publicKey");
long id = set.getLong("id");
boolean banned = set.getBoolean("banned");
return new MySQLUserHardware(hardwareInfo, publicKey == null ? null : IOHelper.read(publicKey.getBinaryStream()), 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 = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey);
s.setBlob(1, new ByteArrayInputStream(publicKey));
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
return fetchHardwareInfo(set);
} else {
return null;
}
}
} catch (SQLException | IOException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData);
try (ResultSet set = s.executeQuery()) {
while (set.next()) {
MySQLUserHardware hw = fetchHardwareInfo(set);
HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info);
if (result.compareLevel > criticalCompareLevel) {
return hw;
}
}
}
} catch (SQLException | IOException throwables) {
logger.error("SQL Error", throwables);
}
return null;
}
@Override
public UserHardware getHardwareInfoById(String id) {
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlFindHardwareById);
s.setLong(1, Long.parseLong(id));
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
return fetchHardwareInfo(set);
} else {
return null;
}
}
} catch (SQLException | IOException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) {
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS);
s.setBlob(1, new ByteArrayInputStream(publicKey));
s.setString(2, hardwareInfo.hwDiskId);
s.setString(3, hardwareInfo.baseboardSerialNumber);
s.setBlob(4, hardwareInfo.displayId == null ? null : new ByteArrayInputStream(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);
return new MySQLUserHardware(hardwareInfo, publicKey, id, false);
}
}
return null;
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
return null;
}
}
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
SQLUserSession mySQLUserSession = (SQLUserSession) userSession;
MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser();
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
if (mySQLUser.hwidId == mySQLUserHardware.id) return;
mySQLUser.hwidId = mySQLUserHardware.id;
try (Connection connection = mySQLHolder.getConnection()) {
setUserHardwareId(connection, mySQLUser.getUUID(), mySQLUserHardware.id);
} catch (SQLException throwables) {
logger.error("SQL Error", throwables);
}
}
@Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
mySQLUserHardware.publicKey = publicKey;
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey);
s.setBlob(1, new ByteArrayInputStream(publicKey));
s.setLong(2, mySQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
List<User> users = new LinkedList<>();
try (Connection c = mySQLHolder.getConnection()) {
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));
}
}
} catch (SQLException e) {
logger.error("SQL error", e);
return null;
}
return users;
}
@Override
public void banHardware(UserHardware hardware) {
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
mySQLUserHardware.banned = true;
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, true);
s.setLong(2, mySQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL Error", e);
}
}
@Override
public void unbanHardware(UserHardware hardware) {
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
mySQLUserHardware.banned = false;
try (Connection connection = mySQLHolder.getConnection()) {
PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned);
s.setBoolean(1, false);
s.setLong(2, mySQLUserHardware.id);
s.executeUpdate();
} catch (SQLException e) {
logger.error("SQL error", e);
}
}
@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 {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public MySQLUserHardware(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 "MySQLUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public static class MySQLUser extends SQLUser {
protected long hwidId;
public MySQLUser(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 "MySQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
", hwidId=" + hwidId +
'}';
}
}
}

View file

@ -1,21 +0,0 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
public PostgreSQLSourceConfig postgresSQLHolder;
@Override
public SQLSourceConfig getSQLConfig() {
return postgresSQLHolder;
}
@Override
public void init(LaunchServer server, AuthProviderPair pair) {
super.init(server, pair);
logger.warn("Method 'postgresql' deprecated and may be removed in future release. Please use new 'sql' method: https://gravitlauncher.com/auth");
}
}

View file

@ -1,57 +0,0 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.AuthException;
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.UUID;
public class RejectAuthCoreProvider extends AuthCoreProvider {
@Override
public User getUserByUsername(String username) {
return null;
}
@Override
public User getUserByUUID(UUID uuid) {
return null;
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) {
return null;
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
return null;
}
@Override
public void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
throw new AuthException("Please configure AuthCoreProvider");
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
throw new AuthException("Please configure AuthCoreProvider");
}
@Override
public User checkServer(Client client, String username, String serverID) {
return null;
}
@Override
public boolean joinServer(Client client, String username, UUID uuid, String accessToken, String serverID) {
return false;
}
@Override
public void close() {
}
}

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,17 +0,0 @@
package pro.gravit.launchserver.auth.core;
import pro.gravit.launcher.base.ClientPermissions;
import java.util.UUID;
public interface User {
String getUsername();
UUID getUUID();
ClientPermissions getPermissions();
default boolean isBanned() {
return false;
}
}

View file

@ -1,11 +0,0 @@
package pro.gravit.launchserver.auth.core;
public interface UserSession {
String getID();
User getUser();
String getMinecraftAccessToken();
long getExpireIn();
}

View file

@ -1,13 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
public interface UserHardware {
HardwareReportRequest.HardwareInfo getHardwareInfo();
byte[] getPublicKey();
String getId();
boolean isBanned();
}

View file

@ -1,4 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
public interface AuthSupport {
}

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

@ -1,10 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
public interface AuthSupportExit extends AuthSupport {
void deleteSession(UserSession session);
void 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

@ -1,9 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
@Feature("users")
public interface AuthSupportGetAllUsers extends AuthSupport {
Iterable<User> getAllUsers();
}

View file

@ -1,98 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.helper.DamerauHelper;
import java.util.Arrays;
public interface AuthSupportHardware extends AuthSupport {
UserHardware getHardwareInfoByPublicKey(byte[] publicKey);
UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info);
UserHardware getHardwareInfoById(String id);
UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey);
void connectUserAndHardware(UserSession userSession, UserHardware hardware);
void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey);
Iterable<User> getUsersByHardwareInfo(UserHardware hardware);
void banHardware(UserHardware hardware);
void unbanHardware(UserHardware hardware);
default void normalizeHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo) {
if (hardwareInfo.baseboardSerialNumber != null)
hardwareInfo.baseboardSerialNumber = hardwareInfo.baseboardSerialNumber.trim();
if (hardwareInfo.hwDiskId != null) hardwareInfo.hwDiskId = hardwareInfo.hwDiskId.trim();
}
//Required normalize HardwareInfo
default HardwareInfoCompareResult compareHardwareInfo(HardwareReportRequest.HardwareInfo first, HardwareReportRequest.HardwareInfo second) {
HardwareInfoCompareResult result = new HardwareInfoCompareResult();
if (first.hwDiskId == null || first.hwDiskId.isEmpty()) result.firstSpoofingLevel += 0.9;
if (first.displayId == null || first.displayId.length < 4) result.firstSpoofingLevel += 0.3;
if (first.baseboardSerialNumber == null || first.baseboardSerialNumber.trim().isEmpty())
result.firstSpoofingLevel += 0.2;
if (second.hwDiskId == null || second.hwDiskId.trim().isEmpty()) result.secondSpoofingLevel += 0.9;
if (second.displayId == null || second.displayId.length < 4) result.secondSpoofingLevel += 0.3;
if (second.baseboardSerialNumber == null || second.baseboardSerialNumber.trim().isEmpty())
result.secondSpoofingLevel += 0.2;
if (first.hwDiskId != null && second.hwDiskId != null) {
int hwDIskIdRate = DamerauHelper.calculateDistance(first.hwDiskId.toLowerCase(), second.hwDiskId.toLowerCase());
if (hwDIskIdRate == 0) // 100% compare
{
result.compareLevel += 0.99;
} else if (hwDIskIdRate < 3) //Very small change
{
result.compareLevel += 0.85;
} else if (hwDIskIdRate < (first.hwDiskId.length() + second.hwDiskId.length()) / 4) {
double addLevel = hwDIskIdRate / ((double) (first.hwDiskId.length() + second.hwDiskId.length()) / 2.0);
if (addLevel > 0.0 && addLevel < 0.85) result.compareLevel += addLevel;
}
}
if (first.baseboardSerialNumber != null && second.baseboardSerialNumber != null) {
int baseboardSerialRate = DamerauHelper.calculateDistance(first.baseboardSerialNumber.toLowerCase(), second.baseboardSerialNumber.toLowerCase());
if (baseboardSerialRate == 0) // 100% compare
{
result.compareLevel += 0.3;
} else if (baseboardSerialRate < 3) //Very small change
{
result.compareLevel += 0.15;
}
}
if (first.displayId != null && second.displayId != null) {
if (Arrays.equals(first.displayId, second.displayId)) {
result.compareLevel += 0.75;
}
}
//Check statistic info
if (first.logicalProcessors == 0 || first.physicalProcessors == 0 || first.logicalProcessors < first.physicalProcessors) //WTF
result.firstSpoofingLevel += 0.9;
if (second.logicalProcessors == 0 || second.physicalProcessors == 0 || second.logicalProcessors < second.physicalProcessors) //WTF
result.secondSpoofingLevel += 0.9;
if (first.physicalProcessors == second.physicalProcessors && first.logicalProcessors == second.logicalProcessors)
result.compareLevel += 0.05;
if (first.battery != second.battery)
result.compareLevel -= 0.05;
if (first.processorMaxFreq == second.processorMaxFreq)
result.compareLevel += 0.1;
if (first.totalMemory == second.totalMemory)
result.compareLevel += 0.1;
if (Math.abs(first.totalMemory - second.totalMemory) < 32 * 1024)
result.compareLevel += 0.05;
return result;
}
class HardwareInfoCompareResult {
public double firstSpoofingLevel = 0.0;
public double secondSpoofingLevel = 0.0;
public double compareLevel;
}
}

View file

@ -1,12 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import pro.gravit.launcher.base.request.auth.AuthRequest;
import pro.gravit.launchserver.auth.Feature;
import pro.gravit.launchserver.auth.core.User;
import java.util.Map;
@Feature("registration")
public interface AuthSupportRegistration extends AuthSupport {
User registration(String login, String email, AuthRequest.AuthPasswordInterface password, Map<String, String> properties);
}

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

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

@ -1,25 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import java.util.Map;
public interface UserSupportAdditionalData {
String getProperty(String name);
default String getPropertyUnprivileged(String name) {
return getProperty(name);
}
default String getPropertyUnprivilegedSelf(String name) {
return getProperty(name);
}
Map<String, String> getPropertiesMap();
default Map<String, String> getPropertiesMapUnprivileged() {
return getPropertiesMap();
}
default Map<String, String> getPropertiesMapUnprivilegedSelf() {
return getPropertiesMapUnprivileged();
}
}

View file

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

View file

@ -1,34 +0,0 @@
package pro.gravit.launchserver.auth.core.interfaces.user;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.Texture;
import java.util.HashMap;
import java.util.Map;
public interface UserSupportTextures {
Texture getSkinTexture();
Texture getCloakTexture();
default Texture getSkinTexture(ClientProfile profile) {
return getSkinTexture();
}
default Texture getCloakTexture(ClientProfile profile) {
return getCloakTexture();
}
default Map<String, Texture> getUserAssets() {
var skin = getSkinTexture();
var cape = getCloakTexture();
Map<String, Texture> map = new HashMap<>();
if (skin != null) {
map.put("SKIN", skin);
}
if (cape != null) {
map.put("CAPE", cape);
}
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,13 +0,0 @@
package pro.gravit.launchserver.auth.password;
public class AcceptPasswordVerifier extends PasswordVerifier {
@Override
public boolean check(String encryptedPassword, String password) {
return true;
}
@Override
public String encrypt(String password) {
return "";
}
}

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

@ -1,41 +0,0 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class DigestPasswordVerifier extends PasswordVerifier {
private transient final Logger logger = LogManager.getLogger();
public String algo;
private byte[] digest(String text) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(algo);
return digest.digest(IOHelper.encode(text));
}
@Override
public boolean check(String encryptedPassword, String password) {
try {
byte[] bytes = SecurityHelper.fromHex(encryptedPassword);
return Arrays.equals(bytes, digest(password));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return false;
}
}
@Override
public String encrypt(String password) {
try {
return SecurityHelper.toHex(digest(password));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return null;
}
}
}

View file

@ -1,43 +0,0 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class DoubleDigestPasswordVerifier extends PasswordVerifier {
private transient final Logger logger = LogManager.getLogger();
public String algo;
public boolean toHexMode;
private byte[] digest(String text) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance(algo);
byte[] firstDigest = digest.digest(IOHelper.encode(text));
return toHexMode ? digest.digest(IOHelper.encode(SecurityHelper.toHex(firstDigest))) : digest.digest(firstDigest);
}
@Override
public boolean check(String encryptedPassword, String password) {
try {
byte[] bytes = SecurityHelper.fromHex(encryptedPassword);
return Arrays.equals(bytes, digest(password));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return false;
}
}
@Override
public String encrypt(String password) {
try {
return SecurityHelper.toHex(digest(password));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return null;
}
}
}

View file

@ -1,81 +0,0 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class JsonPasswordVerifier extends PasswordVerifier {
private static final Logger logger = LogManager.getLogger();
private transient final HttpClient client = HttpClient.newBuilder().build();
public String url;
public String bearerToken;
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
HttpRequest.BodyPublisher publisher;
if (request != null) {
publisher = HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(request));
} else {
publisher = HttpRequest.BodyPublishers.noBody();
}
try {
HttpRequest.Builder request1 = HttpRequest.newBuilder()
.method("POST", publisher)
.uri(new URI(url))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.timeout(Duration.ofMillis(10000));
if (bearerToken != null) {
request1.header("Authorization", "Bearer ".concat(bearerToken));
}
HttpResponse<InputStream> response = client.send(request1.build(), HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode();
if (200 > statusCode || statusCode > 300) {
if (statusCode >= 500) {
logger.error("JsonCoreProvider: {} return {}", url, statusCode);
} else if (statusCode >= 300 && statusCode <= 400) {
logger.error("JsonCoreProvider: {} return {}, try redirect to {}. Redirects not supported!", url, statusCode, response.headers().firstValue("Location").orElse("Unknown"));
} else if (statusCode == 403 || statusCode == 401) {
logger.error("JsonCoreProvider: {} return {}. Please set 'bearerToken'!", url, statusCode);
}
return null;
}
try (Reader reader = new InputStreamReader(response.body())) {
return Launcher.gsonManager.gson.fromJson(reader, clazz);
}
} catch (Exception e) {
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

@ -1,27 +0,0 @@
package pro.gravit.launchserver.auth.password;
import pro.gravit.utils.ProviderMap;
public abstract class PasswordVerifier {
public static final ProviderMap<PasswordVerifier> providers = new ProviderMap<>("PasswordVerifier");
private static boolean registeredProviders = false;
public static void registerProviders() {
if (!registeredProviders) {
providers.register("plain", PlainPasswordVerifier.class);
providers.register("digest", DigestPasswordVerifier.class);
providers.register("doubleDigest", DoubleDigestPasswordVerifier.class);
providers.register("json", JsonPasswordVerifier.class);
providers.register("bcrypt", BCryptPasswordVerifier.class);
providers.register("accept", AcceptPasswordVerifier.class);
providers.register("reject", RejectPasswordVerifier.class);
registeredProviders = true;
}
}
public abstract boolean check(String encryptedPassword, String password);
public String encrypt(String password) {
throw new UnsupportedOperationException();
}
}

View file

@ -1,13 +0,0 @@
package pro.gravit.launchserver.auth.password;
public class PlainPasswordVerifier extends PasswordVerifier {
@Override
public boolean check(String encryptedPassword, String password) {
return encryptedPassword.equals(password);
}
@Override
public String encrypt(String password) {
return super.encrypt(password);
}
}

View file

@ -1,8 +0,0 @@
package pro.gravit.launchserver.auth.password;
public class RejectPasswordVerifier extends PasswordVerifier {
@Override
public boolean check(String encryptedPassword, String password) {
return false;
}
}

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

@ -1,189 +0,0 @@
package pro.gravit.launchserver.auth.protect;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.protect.interfaces.HardwareProtectHandler;
import pro.gravit.launchserver.auth.protect.interfaces.JoinServerProtectHandler;
import pro.gravit.launchserver.auth.protect.interfaces.SecureProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.SECONDS;
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
private transient final Logger logger = LogManager.getLogger();
public boolean enableHardwareFeature;
private transient LaunchServer server;
@Override
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
return event;
}
@Override
public boolean allowGetSecureLevelInfo(Client client) {
return client.checkSign;
}
@Override
public void onHardwareReport(HardwareReportResponse response, Client client) {
if (!enableHardwareFeature) {
response.sendResult(new HardwareReportRequestEvent());
return;
}
if (!client.isAuth || client.trustLevel == null || client.trustLevel.publicKey == null) {
response.sendError("Access denied");
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");
{
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
if (authSupportHardware != null) {
UserHardware hardware = authSupportHardware.getHardwareInfoByData(response.hardware);
if (hardware == null) {
hardware = authSupportHardware.createHardwareInfo(response.hardware, client.trustLevel.publicKey);
} else {
authSupportHardware.addPublicKeyToHardwareInfo(hardware, client.trustLevel.publicKey);
}
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned");
}
client.trustLevel.hardwareInfo = hardware;
response.sendResult(new HardwareReportRequestEvent(createHardwareToken(client.username, hardware), SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)));
} else {
logger.error("AuthCoreProvider not supported hardware");
response.sendError("AuthCoreProvider not supported hardware");
}
}
}
@Override
public VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
if (enableHardwareFeature) {
var authSupportHardware = client.auth.isSupport(AuthSupportHardware.class);
if (authSupportHardware != null) {
UserHardware hardware = authSupportHardware.getHardwareInfoByPublicKey(client.trustLevel.publicKey);
if (hardware == null) //HWID not found?
return new VerifySecureLevelKeyRequestEvent(true, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
if (hardware.isBanned()) {
throw new SecurityException("Your hardware banned");
}
client.trustLevel.hardwareInfo = hardware;
authSupportHardware.connectUserAndHardware(client.sessionObject, hardware);
return new VerifySecureLevelKeyRequestEvent(false, false, createPublicKeyToken(client.username, client.trustLevel.publicKey), SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire));
} else {
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));
}
@Override
public boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
return !enableHardwareFeature || (client.trustLevel != null && client.trustLevel.hardwareInfo != null);
}
@Override
public void init(LaunchServer server) {
this.server = server;
}
public String createHardwareToken(String username, UserHardware hardware) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.hardwareTokenExpire)))
.claim("hardware", hardware.getId())
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
public String createPublicKeyToken(String username, byte[] publicKey) {
return Jwts.builder()
.setIssuer("LaunchServer")
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + SECONDS.toMillis(server.config.netty.security.publicKeyTokenExpire)))
.claim("publicKey", Base64.getEncoder().encodeToString(publicKey))
.signWith(server.keyAgreementManager.ecdsaPrivateKey)
.compact();
}
public static class HardwareInfoTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser;
public HardwareInfoTokenVerifier(LaunchServer server) {
this.parser = Jwts.parser()
.requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
}
@Override
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
try {
var parse = parser.parseClaimsJws(extendedToken);
String hardwareInfoId = parse.getBody().get("hardware", String.class);
if (hardwareInfoId == null) return false;
if (client.auth == null) return false;
var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class);
if (hardwareSupport == null) return false;
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
client.trustLevel.hardwareInfo = hardware;
return true;
} catch (Throwable e) {
logger.error("Hardware JWT error", e);
}
return false;
}
}
public static class PublicKeyTokenVerifier implements RestoreResponse.ExtendedTokenProvider {
private transient final Logger logger = LogManager.getLogger();
private final JwtParser parser;
public PublicKeyTokenVerifier(LaunchServer server) {
this.parser = Jwts.parser()
.requireIssuer("LaunchServer")
.verifyWith(server.keyAgreementManager.ecdsaPublicKey)
.build();
}
@Override
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
try {
var parse = parser.parseClaimsJws(extendedToken);
String publicKey = parse.getBody().get("publicKey", String.class);
if (publicKey == null) return false;
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
client.trustLevel.publicKey = Base64.getDecoder().decode(publicKey);
return true;
} catch (Throwable e) {
logger.error("Public Key JWT error", e);
}
return false;
}
}
}

View file

@ -1,17 +0,0 @@
package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
public class NoProtectHandler extends ProtectHandler {
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return true;
}
@Override
public boolean allowJoinServer(Client client) {
return true;
}
}

View file

@ -1,35 +0,0 @@
package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap;
public abstract class ProtectHandler {
public static final ProviderMap<ProtectHandler> providers = new ProviderMap<>("ProtectHandler");
private static boolean registredHandl = false;
public static void registerHandlers() {
if (!registredHandl) {
providers.register("none", NoProtectHandler.class);
providers.register("std", StdProtectHandler.class);
providers.register("advanced", AdvancedProtectHandler.class);
registredHandl = true;
}
}
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
public boolean allowJoinServer(Client client) {
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT;
}
public void init(LaunchServer server) {
}
public void close() {
}
//public abstract
}

View file

@ -1,59 +0,0 @@
package pro.gravit.launchserver.auth.protect;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.launchserver.socket.response.auth.AuthResponse;
import java.util.*;
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
private transient final Logger logger = LogManager.getLogger();
public Map<String, List<String>> profileWhitelist = new HashMap<>();
public List<String> allowUpdates = new ArrayList<>();
@Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
}
@Override
public void init(LaunchServer server) {
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
}
}
@Override
public boolean canGetProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
}
@Override
public boolean canChangeProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
}
@Override
public boolean canGetUpdates(String updatesDirName, Client client) {
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
}
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if (client.permissions != null) {
String permByUUID = property.formatted(profile.getUUID());
if (client.permissions.hasPerm(permByUUID)) {
return true;
}
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
if (client.permissions.hasPerm(permByTitle)) {
return true;
}
}
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
return allowedUsername != null && allowedUsername.contains(client.username);
}
}

View file

@ -1,8 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
public interface HardwareProtectHandler {
void onHardwareReport(HardwareReportResponse response, Client client);
}

View file

@ -1,11 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launchserver.socket.Client;
import java.util.UUID;
public interface JoinServerProtectHandler {
default boolean onJoinServer(String serverID, String username, UUID uuid, Client client) {
return true;
}
}

View file

@ -1,22 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler {
default boolean canGetProfiles(Client client) {
return true;
}
default boolean canGetProfile(ClientProfile profile, Client client) {
return true;
}
default boolean canChangeProfile(ClientProfile profile, Client client) {
return client.isAuth;
}
default boolean canGetUpdates(String updatesDirName, Client client) {
return true;
}
}

View file

@ -1,39 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.base.events.request.SecurityReportRequestEvent;
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
public interface SecureProtectHandler {
default byte[] generateSecureLevelKey() {
return SecurityHelper.randomBytes(128);
}
default void verifySecureLevelKey(byte[] publicKey, byte[] data, byte[] signature) throws InvalidKeySpecException, SignatureException {
if (publicKey == null || signature == null) throw new InvalidKeySpecException();
ECPublicKey pubKey = SecurityHelper.toPublicECDSAKey(publicKey);
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
sign.update(data);
sign.verify(signature);
}
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
boolean allowGetSecureLevelInfo(Client client);
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client) {
return new SecurityReportRequestEvent();
}
default VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
return new VerifySecureLevelKeyRequestEvent();
}
}

View file

@ -1,68 +0,0 @@
package pro.gravit.launchserver.auth.texture;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
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 bearerToken;
@Override
public void close() {
//None
}
@Override
public Texture getCloakTexture(UUID uuid, String username, String client) {
logger.warn("Ineffective get cloak texture for {}", username);
return getAssets(uuid, username, client).get("CAPE");
}
@Override
public Texture getSkinTexture(UUID uuid, String username, String client) {
logger.warn("Ineffective get skin texture for {}", username);
return getAssets(uuid, username, client).get("SKIN");
}
@Override
public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
try {
Map<String, JsonTexture> map = requester.<Map<String, JsonTexture>>send(requester.get(RequestTextureProvider.getTextureURL(url, uuid, username, client), bearerToken), MAP_TYPE).getOrThrow();
return JsonTexture.convertMap(map);
} catch (IOException e) {
logger.error("JsonTextureProvider", e);
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,78 +0,0 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
public final class RequestTextureProvider extends TextureProvider {
// Instance
public String skinURL;
public String cloakURL;
public String skinLocalPath;
public String cloakLocalPath;
public RequestTextureProvider() {
}
public RequestTextureProvider(String skinURL, String cloakURL) {
this.skinURL = skinURL;
this.cloakURL = cloakURL;
}
private static Texture getTexture(String url, boolean cloak) throws IOException {
try {
return new Texture(url, cloak, null);
} catch (FileNotFoundException ignored) {
return null; // Simply not found
}
}
private static Texture getTexture(String url, Path local, boolean cloak) throws IOException {
try {
return new Texture(url, local, cloak, null);
} catch (FileNotFoundException ignored) {
return null; // Simply not found
}
}
public static String getTextureURL(String url, UUID uuid, String username, String client) {
return CommonHelper.replace(url, "username", IOHelper.urlEncode(username),
"uuid", IOHelper.urlEncode(uuid.toString()), "hash", IOHelper.urlEncode(Launcher.toHash(uuid)),
"client", IOHelper.urlEncode(client == null ? "unknown" : client));
}
@Override
public void close() {
// Do nothing
}
@Override
public Texture getCloakTexture(UUID uuid, String username, String client) throws IOException {
String textureUrl = getTextureURL(cloakURL, uuid, username, client);
if (cloakLocalPath == null) {
return getTexture(textureUrl, true);
} else {
String path = getTextureURL(cloakLocalPath, uuid, username, client);
return getTexture(textureUrl, Paths.get(path), true);
}
}
@Override
public Texture getSkinTexture(UUID uuid, String username, String client) throws IOException {
String textureUrl = getTextureURL(skinURL, uuid, username, client);
if (skinLocalPath == null) {
return getTexture(textureUrl, false);
} else {
String path = getTextureURL(skinLocalPath, uuid, username, client);
return getTexture(textureUrl, Paths.get(path), false);
}
}
}

View file

@ -1,95 +0,0 @@
package pro.gravit.launchserver.auth.texture;
import pro.gravit.launcher.base.profiles.Texture;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public abstract class TextureProvider implements AutoCloseable {
public static final ProviderMap<TextureProvider> providers = new ProviderMap<>("TextureProvider");
private static boolean registredProv = false;
public static void registerProviders() {
if (!registredProv) {
providers.register("null", NullTextureProvider.class);
providers.register("void", VoidTextureProvider.class);
// Auth providers that doesn't do nothing :D
providers.register("request", RequestTextureProvider.class);
providers.register("json", JsonTextureProvider.class);
registredProv = true;
}
}
@Override
public abstract void close() throws IOException;
public abstract Texture getCloakTexture(UUID uuid, String username, String client) throws IOException;
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
@Deprecated
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
Texture skin;
try {
skin = getSkinTexture(uuid, username, client);
} catch (IOException e) {
skin = null;
}
// Get cloak texture
Texture cloak;
try {
cloak = getCloakTexture(uuid, username, client);
} catch (IOException e) {
cloak = null;
}
return new SkinAndCloakTextures(skin, cloak);
}
public Map<String, Texture> getAssets(UUID uuid, String username, String client) {
Texture skin;
try {
skin = getSkinTexture(uuid, username, client);
} catch (IOException e) {
skin = null;
}
// Get cloak texture
Texture cloak;
try {
cloak = getCloakTexture(uuid, username, client);
} catch (IOException e) {
cloak = null;
}
Map<String, Texture> map = new HashMap<>();
if (skin != null) {
map.put("SKIN", skin);
}
if (cloak != null) {
map.put("CAPE", cloak);
}
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,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

@ -1,96 +0,0 @@
package pro.gravit.launchserver.binary;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.CommonHelper;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class BinaryPipeline {
public final List<LauncherBuildTask> tasks = new ArrayList<>();
public final Path buildDir;
public final String nameFormat;
protected transient final Logger logger = LogManager.getLogger();
public BinaryPipeline(Path buildDir, String nameFormat) {
this.buildDir = buildDir;
this.nameFormat = nameFormat;
}
public void addCounted(int count, Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
List<LauncherBuildTask> indexes = new ArrayList<>();
tasks.stream().filter(pred).forEach(indexes::add);
indexes.forEach(e -> tasks.add(tasks.indexOf(e) + count, taskAdd));
}
public void replaceCounted(int count, Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
List<LauncherBuildTask> indexes = new ArrayList<>();
tasks.stream().filter(pred).forEach(indexes::add);
indexes.forEach(e -> tasks.set(tasks.indexOf(e) + count, taskRep));
}
public void addPre(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
addCounted(-1, pred, taskAdd);
}
public void add(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
addCounted(0, pred, taskAdd);
}
public void addAfter(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskAdd) {
addCounted(1, pred, taskAdd);
}
public void replacePre(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
replaceCounted(-1, pred, taskRep);
}
public void replace(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
replaceCounted(0, pred, taskRep);
}
public void replaceAfter(Predicate<LauncherBuildTask> pred, LauncherBuildTask taskRep) {
replaceCounted(1, pred, taskRep);
}
public <T extends LauncherBuildTask> List<T> getTasksByClass(Class<T> taskClass) {
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).collect(Collectors.toList());
}
public <T extends LauncherBuildTask> Optional<T> getTaskByClass(Class<T> taskClass) {
return tasks.stream().filter(taskClass::isInstance).map(taskClass::cast).findFirst();
}
public Optional<LauncherBuildTask> getTaskBefore(Predicate<LauncherBuildTask> pred) {
LauncherBuildTask last = null;
for(var e : tasks) {
if(pred.test(e)) {
return Optional.ofNullable(last);
}
last = e;
}
return Optional.empty();
}
public String nextName(String taskName) {
return nameFormat.formatted(taskName);
}
public Path nextPath(String taskName) {
return buildDir.resolve(nextName(taskName));
}
public Path nextPath(LauncherBuildTask task) {
return nextPath(task.getName());
}
public Path nextLowerPath(LauncherBuildTask task) {
return nextPath(CommonHelper.low(task.getName()));
}
}

View file

@ -1,281 +0,0 @@
package pro.gravit.launchserver.binary;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launcher.core.serialize.stream.StreamObject;
import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import static pro.gravit.utils.helper.IOHelper.newZipEntry;
public class BuildContext {
public final ZipOutputStream output;
public final List<JarFile> readerClassPath;
public final MainBuildTask task;
public final HashSet<String> fileList;
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) {
this.output = output;
this.readerClassPath = readerClassPath;
this.task = task;
this.runtimeDir = runtimeDir;
fileList = new HashSet<>(1024);
clientModules = new HashSet<>();
legacyClientModules = new HashSet<>();
}
public void pushFile(String filename, InputStream inputStream) throws IOException {
ZipEntry zip = IOHelper.newZipEntry(filename);
output.putNextEntry(zip);
IOHelper.transfer(inputStream, output);
output.closeEntry();
fileList.add(filename);
}
public void pushFile(String filename, StreamObject object) throws IOException {
ZipEntry zip = IOHelper.newZipEntry(filename);
output.putNextEntry(zip);
object.write(new HOutput(output));
output.closeEntry();
fileList.add(filename);
}
public void pushFile(String filename, Object object, Type type) throws IOException {
ZipEntry zip = IOHelper.newZipEntry(filename);
output.putNextEntry(zip);
try (BufferedWriter w = IOHelper.newWriter(IOHelper.nonClosing(output))) {
Launcher.gsonManager.gson.toJson(object, type);
}
output.closeEntry();
fileList.add(filename);
pushBytes(filename, IOHelper.encode(Launcher.gsonManager.gson.toJson(object, type)));
}
public void pushDir(Path dir, String targetDir, Map<String, byte[]> hashMap, boolean hidden) throws IOException {
IOHelper.walk(dir, new RuntimeDirVisitor(output, hashMap, dir, targetDir), hidden);
}
public void pushEncryptedDir(Path dir, String targetDir, String aesHexKey, Map<String, byte[]> hashMap, boolean hidden) throws IOException {
IOHelper.walk(dir, new EncryptedRuntimeDirVisitor(output, aesHexKey, hashMap, dir, targetDir), hidden);
}
public void pushBytes(String filename, byte[] bytes) throws IOException {
ZipEntry zip = IOHelper.newZipEntry(filename);
output.putNextEntry(zip);
output.write(bytes);
output.closeEntry();
fileList.add(filename);
}
public void pushJarFile(Path jarfile, Predicate<ZipEntry> filter, Predicate<String> needTransform) throws IOException {
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 {
try (ZipInputStream input = new ZipInputStream(IOHelper.newInput(jarfile))) {
ZipEntry e = input.getNextEntry();
while (e != null) {
String filename = e.getName();
if (e.isDirectory() || fileList.contains(filename) || filter.test(e)) {
e = input.getNextEntry();
continue;
}
output.putNextEntry(IOHelper.newZipEntry(e));
if (filename.endsWith(".class")) {
String classname = filename.replace('/', '.').substring(0,
filename.length() - ".class".length());
if (!needTransform.test(classname)) {
IOHelper.transfer(input, output);
} else {
byte[] bytes = IOHelper.read(input);
bytes = task.transformClass(bytes, classname, this);
output.write(bytes);
}
} else
IOHelper.transfer(input, output);
fileList.add(filename);
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 ZipOutputStream output;
private final Map<String, byte[]> hashs;
private final Path sourceDir;
private final String targetDir;
private RuntimeDirVisitor(ZipOutputStream output, Map<String, byte[]> hashs, Path sourceDir, String targetDir) {
this.output = output;
this.hashs = hashs;
this.sourceDir = sourceDir;
this.targetDir = targetDir;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String dirName = IOHelper.toString(sourceDir.relativize(dir));
output.putNextEntry(newEntry(dirName + '/'));
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String fileName = IOHelper.toString(sourceDir.relativize(file));
if (hashs != null)
hashs.put(fileName, SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file));
// Create zip entry and transfer contents
output.putNextEntry(newEntry(fileName));
IOHelper.transfer(file, output);
// Return result
return super.visitFile(file, attrs);
}
private ZipEntry newEntry(String fileName) {
return newZipEntry(targetDir + IOHelper.CROSS_SEPARATOR + fileName);
}
}
private final static class EncryptedRuntimeDirVisitor extends SimpleFileVisitor<Path> {
private final ZipOutputStream output;
private final Map<String, byte[]> hashs;
private final Path sourceDir;
private final String targetDir;
private final SecretKeySpec sKeySpec;
private final IvParameterSpec iKeySpec;
private final transient Logger logger = LogManager.getLogger();
private EncryptedRuntimeDirVisitor(ZipOutputStream output, String aesKey, Map<String, byte[]> hashs, Path sourceDir, String targetDir) {
this.output = output;
this.hashs = hashs;
this.sourceDir = sourceDir;
this.targetDir = targetDir;
try {
byte[] key = SecurityHelper.fromHex(aesKey);
byte[] compatKey = SecurityHelper.getAESKey(key);
sKeySpec = new SecretKeySpec(compatKey, "AES/CBC/PKCS5Padding");
iKeySpec = new IvParameterSpec(IOHelper.encode("8u3d90ikr7o67lsq"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
byte[] digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.MD5, file);
String fileName = IOHelper.toString(sourceDir.relativize(file));
if (hashs != null) {
hashs.put(fileName, digest);
}
// Create zip entry and transfer contents
try {
output.putNextEntry(newEntry(SecurityHelper.toHex(digest)));
} catch (ZipException e) {
return super.visitFile(file, attrs); // fix duplicate files
}
Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {
IOHelper.transfer(file, stream);
}
// Return result
return super.visitFile(file, attrs);
}
private ZipEntry newEntry(String fileName) {
return newZipEntry(targetDir + IOHelper.CROSS_SEPARATOR + fileName);
}
private static class NoCloseOutputStream extends OutputStream {
private final OutputStream stream;
private NoCloseOutputStream(OutputStream stream) {
this.stream = stream;
}
@Override
public void write(int i) throws IOException {
stream.write(i);
}
@Override
public void write(byte[] b) throws IOException {
stream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b, off, len);
}
@Override
public void flush() throws IOException {
stream.flush();
}
}
}
}

View file

@ -1,22 +0,0 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Files;
public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe");
}
@Override
public void build() throws IOException {
if (IOHelper.isFile(syncBinaryFile)) {
Files.delete(syncBinaryFile);
}
}
}

View file

@ -1,49 +0,0 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public final class JARLauncherBinary extends LauncherBinary {
public final AtomicLong count;
public final Path runtimeDir;
public final Path buildDir;
public final List<Path> coreLibs;
public final List<Path> addonLibs;
public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s.jar");
count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
buildDir = server.dir.resolve("build");
coreLibs = new ArrayList<>();
addonLibs = new ArrayList<>();
files = new HashMap<>();
if (!Files.isDirectory(buildDir)) {
Files.deleteIfExists(buildDir);
Files.createDirectory(buildDir);
}
}
@Override
public void init() {
tasks.add(new PrepareBuildTask(server));
if (!server.config.sign.enabled) tasks.add(new CertificateAutogenTask(server));
tasks.add(new MainBuildTask(server));
tasks.add(new AttachJarsTask(server));
tasks.add(new AdditionalFixesApplyTask(server));
if (server.config.launcher.compress) tasks.add(new CompressBuildTask(server));
tasks.add(new SignJarTask(server.config.sign, server));
}
}

View file

@ -1,69 +0,0 @@
package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server;
public final Path syncBinaryFile;
private volatile byte[] digest;
protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) {
super(server.tmpDir.resolve("build"), nameFormat);
this.server = server;
syncBinaryFile = binaryFile;
}
public static Path resolve(LaunchServer server, String ext) {
return Path.of(server.config.binaryName + ext);
}
public void build() throws IOException {
logger.info("Building launcher binary file");
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() {
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
}
public final byte[] getDigest() {
return digest;
}
public void init() {
}
public final boolean sync() {
try {
var target = syncBinaryFile.toString();
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
return true;
} catch (Throwable e) {
return false;
}
}
}

View file

@ -1,278 +0,0 @@
package pro.gravit.launchserver.binary;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import pro.gravit.launchserver.helper.SignHelper;
import pro.gravit.utils.helper.IOHelper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Generator of signed Jars. It stores some data in memory therefore it is not suited for creation of large files. The
* usage:
* <pre>
* KeyStore keystore = KeyStore.getInstance("JKS");
* keyStore.load(keystoreStream, "keystorePassword");
* SignerJar jar = new SignerJar(out, keyStore, "keyAlias", "keyPassword");
* signedJar.addManifestAttribute("Main-Class", "com.example.MainClass");
* signedJar.addManifestAttribute("Application-Name", "Example");
* signedJar.addManifestAttribute("Permissions", "all-permissions");
* signedJar.addManifestAttribute("Codebase", "*");
* signedJar.addFileContents("com/example/MainClass.class", clsData);
* signedJar.addFileContents("JNLP-INF/APPLICATION.JNLP", generateJnlpContents());
* signedJar.close();
* </pre>
*/
public class SignerJar implements AutoCloseable {
private static final String MANIFEST_FN = "META-INF/MANIFEST.MF";
private static final String DIGEST_HASH = SignHelper.hashFunctionName + "-Digest";
private final String SIG_FN;
private final String SIG_KEY_FN;
private final ZipOutputStream zos;
private final Map<String, String> manifestAttributes;
private final Map<String, String> fileDigests;
private final Map<String, String> sectionDigests;
private final Supplier<CMSSignedDataGenerator> gen;
private String manifestHash;
private String manifestMainHash;
public SignerJar(ZipOutputStream out, Supplier<CMSSignedDataGenerator> gen, String sig_fn, String sig_key_fn) {
zos = out;
this.gen = gen;
manifestAttributes = new LinkedHashMap<>();
fileDigests = new LinkedHashMap<>();
sectionDigests = new LinkedHashMap<>();
SIG_FN = "META-INF/".concat(sig_fn);
SIG_KEY_FN = "META-INF/".concat(sig_key_fn);
}
/**
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
* the stream is closed.
*
* @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(String filename, byte[] contents) throws IOException {
addFileContents(filename, new ByteArrayInputStream(contents));
}
/**
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
* the stream is closed.
*
* @param filename name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(String filename, InputStream contents) throws IOException {
addFileContents(IOHelper.newZipEntry(filename), contents);
}
/**
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
* the stream is closed.
*
* @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(ZipEntry entry, byte[] contents) throws IOException {
addFileContents(entry, new ByteArrayInputStream(contents));
}
/**
* Adds a file to the JAR. The file is immediately added to the zipped output stream. This method cannot be called once
* the stream is closed.
*
* @param entry name of the file to add (use forward slash as a path separator)
* @param contents contents of the file
* @throws NullPointerException if any of the arguments is {@code null}
*/
public void addFileContents(ZipEntry entry, InputStream contents) throws IOException {
zos.putNextEntry(entry);
SignHelper.HashingOutputStream out = new SignHelper.HashingNonClosingOutputStream(zos, SignHelper.hasher());
IOHelper.transfer(contents, out);
zos.closeEntry();
fileDigests.put(entry.getName(), Base64.getEncoder().encodeToString(out.digest()));
}
/**
* Adds a header to the manifest of the JAR.
*
* @param name name of the attribute, it is placed into the main section of the manifest file
* @param value value of the attribute
*/
public void addManifestAttribute(String name, String value) {
manifestAttributes.put(name, value);
}
/**
* Closes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It closes the
* underlying stream.
*
* @throws RuntimeException if the signing goes wrong
*/
@Override
public void close() throws IOException {
finish();
zos.close();
}
/**
* Finishes the JAR file by writing the manifest and signature data to it and finishing the ZIP entries. It leaves the
* underlying stream open.
*
* @throws RuntimeException if the signing goes wrong
*/
public void finish() throws IOException {
writeManifest();
byte[] sig = writeSigFile();
writeSignature(sig);
zos.finish();
}
public ZipOutputStream getZos() {
return zos;
}
/**
* Helper for {@link #writeManifest()} that creates the digest of one entry.
*/
private String hashEntrySection(String name, Attributes attributes) throws IOException {
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
ByteArrayOutputStream o = new ByteArrayOutputStream();
manifest.write(o);
int emptyLen = o.toByteArray().length;
manifest.getEntries().put(name, attributes);
manifest.write(o);
byte[] ob = o.toByteArray();
ob = Arrays.copyOfRange(ob, emptyLen, ob.length);
return Base64.getEncoder().encodeToString(SignHelper.hasher().digest(ob));
}
/**
* Helper for {@link #writeManifest()} that creates the digest of the main section.
*/
private String hashMainSection(Attributes attributes) throws IOException {
Manifest manifest = new Manifest();
manifest.getMainAttributes().putAll(attributes);
SignHelper.HashingOutputStream o = new SignHelper.HashingNonClosingOutputStream(SignHelper.NULL, SignHelper.hasher());
manifest.write(o);
return Base64.getEncoder().encodeToString(o.digest());
}
/**
* Returns the CMS signed data.
*/
private byte[] signSigFile(byte[] sigContents) throws Exception {
CMSSignedDataGenerator gen = this.gen.get();
CMSTypedData cmsData = new CMSProcessableByteArray(sigContents);
CMSSignedData signedData = gen.generate(cmsData, false);
return signedData.getEncoded();
}
/**
* Writes the manifest to the JAR. It also calculates the digests that are required to be placed in the the signature
* file.
*
*/
private void writeManifest() throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(MANIFEST_FN));
Manifest man = new Manifest();
// main section
Attributes mainAttributes = man.getMainAttributes();
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
for (Map.Entry<String, String> entry : manifestAttributes.entrySet())
mainAttributes.put(new Attributes.Name(entry.getKey()), entry.getValue());
// individual files sections
Attributes.Name digestAttr = new Attributes.Name(DIGEST_HASH);
for (Map.Entry<String, String> entry : fileDigests.entrySet()) {
Attributes attributes = new Attributes();
man.getEntries().put(entry.getKey(), attributes);
attributes.put(digestAttr, entry.getValue());
sectionDigests.put(entry.getKey(), hashEntrySection(entry.getKey(), attributes));
}
SignHelper.HashingOutputStream out = new SignHelper.HashingNonClosingOutputStream(zos, SignHelper.hasher());
man.write(out);
zos.closeEntry();
manifestHash = Base64.getEncoder().encodeToString(out.digest());
manifestMainHash = hashMainSection(man.getMainAttributes());
}
/**
* Writes the .SIG file to the JAR.
*
* @return the contents of the file as bytes
*/
private byte[] writeSigFile() throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(SIG_FN));
Manifest man = new Manifest();
// main section
Attributes mainAttributes = man.getMainAttributes();
mainAttributes.put(Attributes.Name.SIGNATURE_VERSION, "1.0");
mainAttributes.put(new Attributes.Name(DIGEST_HASH + "-Manifest"), manifestHash);
mainAttributes.put(new Attributes.Name(DIGEST_HASH + "-Manifest-Main-Attributes"), manifestMainHash);
// individual files sections
Attributes.Name digestAttr = new Attributes.Name(DIGEST_HASH);
for (Map.Entry<String, String> entry : sectionDigests.entrySet()) {
Attributes attributes = new Attributes();
man.getEntries().put(entry.getKey(), attributes);
attributes.put(digestAttr, entry.getValue());
}
man.write(zos);
zos.closeEntry();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
man.write(baos);
return baos.toByteArray();
}
/**
* Signs the .SIG file and writes the signature (.RSA file) to the JAR.
*
* @throws RuntimeException if the signing failed
*/
private void writeSignature(byte[] sigFile) throws IOException {
zos.putNextEntry(IOHelper.newZipEntry(SIG_KEY_FN));
try {
byte[] signature = signSigFile(sigFile);
zos.write(signature);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Signing failed.", e);
}
zos.closeEntry();
}
}

View file

@ -1,77 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.SafeClassWriter;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class AdditionalFixesApplyTask implements LauncherBuildTask {
private final LaunchServer server;
public AdditionalFixesApplyTask(LaunchServer server) {
this.server = server;
}
public static void apply(Path inputFile, Path addFile, ZipOutputStream output, LaunchServer srv, Predicate<ZipEntry> excluder, boolean needFixes) throws IOException {
try (ClassMetadataReader reader = new ClassMetadataReader()) {
reader.getCp().add(new JarFile(inputFile.toFile()));
try (ZipInputStream input = IOHelper.newZipInput(addFile)) {
ZipEntry e = input.getNextEntry();
while (e != null) {
if (e.isDirectory() || excluder.test(e)) {
e = input.getNextEntry();
continue;
}
String filename = e.getName();
output.putNextEntry(IOHelper.newZipEntry(e));
if (filename.endsWith(".class")) {
byte[] bytes;
if (needFixes) {
bytes = classFix(input, reader, srv.config.launcher.stripLineNumbers);
output.write(bytes);
} else
IOHelper.transfer(input, output);
} else
IOHelper.transfer(input, output);
e = input.getNextEntry();
}
}
}
}
private static byte[] classFix(InputStream input, ClassMetadataReader reader, boolean stripNumbers) throws IOException {
ClassReader cr = new ClassReader(input);
ClassNode cn = new ClassNode();
cr.accept(cn, stripNumbers ? (ClassReader.SKIP_DEBUG) : 0);
ClassWriter cw = new SafeClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
@Override
public String getName() {
return "AdditionalFixesApply";
}
@Override
public Path process(Path inputFile) throws IOException {
Path out = server.launcherBinary.nextPath("post-fixed");
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(out))) {
apply(inputFile, inputFile, output, server, (e) -> false, true);
}
return out;
}
}

View file

@ -1,78 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class AttachJarsTask implements LauncherBuildTask {
private final LaunchServer srv;
private final List<Path> jars;
private final List<String> exclusions;
public AttachJarsTask(LaunchServer srv) {
this.srv = srv;
jars = new ArrayList<>();
exclusions = new ArrayList<>();
exclusions.add("META-INF");
exclusions.add("module-info.class");
exclusions.add("LICENSE");
exclusions.add("LICENSE.txt");
}
@Override
public String getName() {
return "AttachJars";
}
@Override
public Path process(Path inputFile) throws IOException {
Path outputFile = srv.launcherBinary.nextPath("attached");
try (ZipInputStream input = IOHelper.newZipInput(inputFile);
ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputFile))) {
ZipEntry e = input.getNextEntry();
while (e != null) {
if (e.isDirectory()) {
e = input.getNextEntry();
continue;
}
output.putNextEntry(IOHelper.newZipEntry(e));
IOHelper.transfer(input, output);
e = input.getNextEntry();
}
attach(output, inputFile, srv.launcherBinary.coreLibs);
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;
}
private void attach(ZipOutputStream output, Path inputFile, List<Path> lst) throws IOException {
for (Path p : lst) {
AdditionalFixesApplyTask.apply(inputFile, p, output, srv, (e) -> filter(e.getName()), false);
}
}
private boolean filter(String name) {
if (name.startsWith("META-INF/services")) return false;
return exclusions.stream().anyMatch(name::startsWith);
}
public List<Path> getJars() {
return jars;
}
public List<String> getExclusions() {
return exclusions;
}
}

View file

@ -1,84 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.helper.SignHelper;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
public class CertificateAutogenTask implements LauncherBuildTask {
private final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
public X509Certificate certificate;
public X509CertificateHolder bcCertificate;
public CMSSignedDataGenerator signedDataGenerator;
public CertificateAutogenTask(LaunchServer server) {
this.server = server;
}
@Override
public String getName() {
return "CertificateAutogen";
}
@Override
public Path process(Path inputFile) throws IOException {
if (signedDataGenerator != null) return inputFile;
try {
logger.warn("You are using an auto-generated certificate (sign.enabled false). It is not good");
logger.warn("It is highly recommended that you use the correct certificate (sign.enabled true)");
logger.warn("You can use GenerateCertificateModule or your own certificate.");
X500NameBuilder subject = new X500NameBuilder();
subject.addRDN(BCStyle.CN, server.config.projectName.concat(" Autogenerated"));
subject.addRDN(BCStyle.O, server.config.projectName);
LocalDateTime startDate = LocalDate.now().atStartOfDay();
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
subject.build(),
new BigInteger("0"),
Date.from(startDate.atZone(ZoneId.systemDefault()).toInstant()),
Date.from(startDate.plusDays(3650).atZone(ZoneId.systemDefault()).toInstant()),
new X500Name("CN=ca"),
SubjectPublicKeyInfo.getInstance(server.keyAgreementManager.ecdsaPublicKey.getEncoded()));
builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeId.id_kp_codeSigning));
//builder.addExtension(Extension.keyUsage, false, new KeyUsage(1));
JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256WITHECDSA");
ContentSigner signer = csBuilder.build(server.keyAgreementManager.ecdsaPrivateKey);
bcCertificate = builder.build(signer);
certificate = new JcaX509CertificateConverter().setProvider("BC")
.getCertificate(bcCertificate);
ArrayList<Certificate> chain = new ArrayList<>();
chain.add(certificate);
signedDataGenerator = SignHelper.createSignedDataGenerator(server.keyAgreementManager.ecdsaPrivateKey, certificate, chain, "SHA256WITHECDSA");
} catch (OperatorCreationException | CMSException | CertificateException e) {
logger.error("Certificate generate failed", e);
}
return inputFile;
}
}

View file

@ -1,46 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class CompressBuildTask implements LauncherBuildTask {
public transient final LaunchServer server;
public CompressBuildTask(LaunchServer server) {
this.server = server;
}
@Override
public String getName() {
return "compress";
}
@Override
public Path process(Path inputFile) throws IOException {
Path output = server.launcherBinary.nextPath(this);
try (ZipOutputStream outputStream = new ZipOutputStream(IOHelper.newOutput(output))) {
outputStream.setMethod(ZipOutputStream.DEFLATED);
outputStream.setLevel(Deflater.BEST_COMPRESSION);
try (ZipInputStream input = IOHelper.newZipInput(inputFile)) {
ZipEntry e = input.getNextEntry();
while (e != null) {
if (e.isDirectory()) {
e = input.getNextEntry();
continue;
}
outputStream.putNextEntry(IOHelper.newZipEntry(e));
IOHelper.transfer(input, outputStream);
e = input.getNextEntry();
}
}
}
return output;
}
}

View file

@ -1,10 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import java.io.IOException;
import java.nio.file.Path;
public interface LauncherBuildTask {
String getName();
Path process(Path inputFile) throws IOException;
}

View file

@ -1,246 +0,0 @@
package pro.gravit.launchserver.binary.tasks;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.LauncherConfig;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.asm.ClassMetadataReader;
import pro.gravit.launchserver.asm.InjectClassAcceptor;
import pro.gravit.launchserver.asm.SafeClassWriter;
import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.utils.HookException;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.security.cert.CertificateEncodingException;
import java.util.*;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;
public class MainBuildTask implements LauncherBuildTask {
public final ClassMetadataReader reader;
public final Set<String> blacklist = new HashSet<>();
public final List<Transformer> transformers = new ArrayList<>();
public final IOHookSet<BuildContext> preBuildHook = new IOHookSet<>();
public final IOHookSet<BuildContext> postBuildHook = new IOHookSet<>();
public final Map<String, Object> properties = new HashMap<>();
private final LaunchServer server;
private transient final Logger logger = LogManager.getLogger();
public MainBuildTask(LaunchServer srv) {
server = srv;
reader = new ClassMetadataReader();
InjectClassAcceptor injectClassAcceptor = new InjectClassAcceptor(properties);
transformers.add(injectClassAcceptor);
}
@Override
public String getName() {
return "MainBuild";
}
@Override
public Path process(Path inputJar) throws IOException {
Path outputJar = server.launcherBinary.nextPath(this);
try (ZipOutputStream output = new ZipOutputStream(IOHelper.newOutput(outputJar))) {
BuildContext context = new BuildContext(output, reader.getCp(), this, server.launcherBinary.runtimeDir);
initProps();
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()));
postInitProps();
reader.getCp().add(new JarFile(inputJar.toFile()));
for (Path e : server.launcherBinary.coreLibs) {
reader.getCp().add(new JarFile(e.toFile()));
}
context.pushJarFile(inputJar, (e) -> blacklist.contains(e.getName()) || e.getName().startsWith("pro/gravit/launcher/debug/"), (e) -> true);
// map for guard
Map<String, byte[]> runtime = new HashMap<>(256);
// Write launcher guard dir
if (server.config.launcher.encryptRuntime) {
context.pushEncryptedDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, server.runtime.runtimeEncryptKey, runtime, false);
} else {
context.pushDir(context.getRuntimeDir(), Launcher.RUNTIME_DIR, runtime, false);
}
if(context.isDeleteRuntimeDir()) {
IOHelper.deleteDir(context.getRuntimeDir(), true);
}
LauncherConfig launcherConfig = new LauncherConfig(server.config.netty.address, server.keyAgreementManager.ecdsaPublicKey, server.keyAgreementManager.rsaPublicKey, runtime, server.config.projectName);
context.pushFile(Launcher.CONFIG_FILE, launcherConfig);
postBuildHook.hook(context);
}
reader.close();
return outputJar;
}
protected void postInitProps() {
List<byte[]> certificates = Arrays.stream(server.certificateManager.trustManager.getTrusted()).map(e -> {
try {
return e.getEncoded();
} catch (CertificateEncodingException e2) {
logger.error("Certificate encoding failed", e2);
return new byte[0];
}
}).collect(Collectors.toList());
if (!server.config.sign.enabled) {
CertificateAutogenTask task = server.launcherBinary.getTaskByClass(CertificateAutogenTask.class).get();
try {
certificates.add(task.certificate.getEncoded());
} catch (CertificateEncodingException e) {
throw new InternalError(e);
}
}
properties.put("launchercore.certificates", certificates);
}
protected void initProps() {
properties.clear();
properties.put("launcher.address", server.config.netty.address);
properties.put("launcher.projectName", server.config.projectName);
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
properties.put("launchercore.env", server.config.env);
properties.put("launcher.memory", server.config.launcher.memoryLimit);
properties.put("launcher.customJvmOptions", server.config.launcher.customJvmOptions);
if (server.config.launcher.encryptRuntime) {
if (server.runtime.runtimeEncryptKey == null)
server.runtime.runtimeEncryptKey = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.runtimeEncryptKey", server.runtime.runtimeEncryptKey);
}
properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey);
String launcherSalt = SecurityHelper.randomStringToken();
byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
server.runtime.clientCheckSecret.concat(".").concat(launcherSalt));
properties.put("runtimeconfig.secureCheckHash", Base64.getEncoder().encodeToString(launcherSecureHash));
properties.put("runtimeconfig.secureCheckSalt", launcherSalt);
if (server.runtime.unlockSecret == null) server.runtime.unlockSecret = SecurityHelper.randomStringToken();
properties.put("runtimeconfig.unlockSecret", server.runtime.unlockSecret);
server.runtime.buildNumber++;
properties.put("runtimeconfig.buildNumber", server.runtime.buildNumber);
}
public byte[] transformClass(byte[] bytes, String classname, BuildContext context) {
byte[] result = bytes;
ClassWriter writer;
ClassNode cn = null;
for (Transformer t : transformers) {
if (t instanceof ASMTransformer asmTransformer) {
if (cn == null) {
ClassReader cr = new ClassReader(result);
cn = new ClassNode();
cr.accept(cn, 0);
}
asmTransformer.transform(cn, classname, context);
continue;
} else if (cn != null) {
writer = new SafeClassWriter(reader, 0);
cn.accept(writer);
result = writer.toByteArray();
}
byte[] old_result = result;
result = t.transform(result, classname, context);
if (old_result != result) {
cn = null;
}
}
if (cn != null) {
writer = new SafeClassWriter(reader, 0);
cn.accept(writer);
result = writer.toByteArray();
}
return result;
}
@FunctionalInterface
public interface Transformer {
byte[] transform(byte[] input, String classname, BuildContext context);
}
public interface ASMTransformer extends Transformer {
default byte[] transform(byte[] input, String classname, BuildContext context) {
ClassReader reader = new ClassReader(input);
ClassNode cn = new ClassNode();
reader.accept(cn, 0);
transform(cn, classname, context);
SafeClassWriter writer = new SafeClassWriter(context.task.reader, 0);
cn.accept(writer);
return writer.toByteArray();
}
void transform(ClassNode cn, String classname, BuildContext context);
}
public static class IOHookSet<R> {
public final Set<IOHook<R>> list = new HashSet<>();
public void registerHook(IOHook<R> hook) {
list.add(hook);
}
public boolean unregisterHook(IOHook<R> hook) {
return list.remove(hook);
}
/**
* @param context custom param
* False to continue
* @throws HookException The hook may return the error text throwing this exception
*/
public void hook(R context) throws HookException, IOException {
for (IOHook<R> hook : list) {
hook.hook(context);
}
}
@FunctionalInterface
public interface IOHook<R> {
/**
* @param context custom param
* False to continue processing hook
* @throws HookException The hook may return the error text throwing this exception
*/
void hook(R context) throws HookException, IOException;
}
}
public abstract static class ASMAnnotationFieldProcessor implements ASMTransformer {
private final String desc;
protected ASMAnnotationFieldProcessor(String desc) {
this.desc = desc;
}
@Override
public void transform(ClassNode cn, String classname, BuildContext context) {
for (FieldNode fn : cn.fields) {
if (fn.invisibleAnnotations == null || fn.invisibleAnnotations.isEmpty()) continue;
AnnotationNode found = null;
for (AnnotationNode an : fn.invisibleAnnotations) {
if (an == null) continue;
if (desc.equals(an.desc)) {
found = an;
break;
}
}
if (found != null) {
transformField(found, fn, cn, classname, context);
}
}
}
abstract public void transformField(AnnotationNode an, FieldNode fn, ClassNode cn, String classname, BuildContext context);
}
}

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