Compare commits

..

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

715 changed files with 17595 additions and 25354 deletions

84
.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
*.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
.gitignore text
# 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 *.dll binary
*.ear binary
*.jar binary
*.so binary *.so binary
*.war binary *.exe binary
*.jks binary
mvnw text eol=lf *.gitattributes text eol=crlf
gradlew text eol=lf *.gitignore text eol=crlf

View file

@ -1,81 +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
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 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/*

1
.gitignore vendored
View file

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

51
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,51 @@
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
CI_VERSION: '6.6.$CI_PIPELINE_IID'
stages:
- build
- test
build:
image: gradle:jdk11
stage: build
before_script:
- apt-get -y update
- apt-get -y install zip git
- export GRADLE_USER_HOME=`pwd`/.gradle
- chmod +x gradlew
- sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
- git submodule sync
- git submodule update --init --recursive
script:
- ./gradlew assemble
after_script:
- mkdir -p artifacts/modules
- cd LaunchServer/build/libs/
- zip -r -9 ../../../artifacts/libraries.zip * -x "LaunchServer.jar" -x "LaunchServer-clean.jar"
- mv LaunchServer.jar ../../../artifacts/LaunchServer.jar
- cd ../../../ServerWrapper/build/libs
- mv ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
- cd ../../../LauncherAuthlib/build/libs
- mv LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
- cd ../../../
- mv modules/*_module/build/libs/*.jar artifacts/modules
- mv modules/*_swmodule/build/libs/*.jar artifacts/modules
- mv modules/*_lmodule/build/libs/*.jar artifacts/modules
cache:
paths:
- .gradle
artifacts:
expire_in: 6 week
paths:
- artifacts
test:
image: gradle:jdk11
stage: test
script:
- ./gradlew check

2
.gitmodules vendored
View file

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

22
.travis.yml Normal file
View file

@ -0,0 +1,22 @@
language: java
dist: trusty
jdk:
- openjdk11
# Use https (public access) instead of git for git-submodules. This modifies only Travis-CI behavior!
# disable the default submodule logic
git:
submodules: false
# use sed to replace the SSH URL with the public URL, then init and update submodules
before_install:
- sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
- git submodule update --init --recursive
# gradle
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 build

View file

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

View file

@ -1,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,4 +1,4 @@
def mainClassName = "pro.gravit.launchserver.Main" def mainClassName = "pro.gravit.launchserver.LaunchServerStarter"
def mainAgentName = "pro.gravit.launchserver.StarterAgent" def mainAgentName = "pro.gravit.launchserver.StarterAgent"
evaluationDependsOn(':Launcher') evaluationDependsOn(':Launcher')
@ -8,42 +8,35 @@
url "https://oss.sonatype.org/content/repositories/snapshots" url "https://oss.sonatype.org/content/repositories/snapshots"
} }
maven { maven {
url "https://jcenter.bintray.com/" url "https://maven.geomajas.org/"
}
maven {
url "https://jitpack.io/"
}
maven {
url 'https://maven.gravit-support.ru/repository/jitpack'
credentials {
username = 'gravitlauncher'
password = 'gravitlauncher'
}
} }
} }
sourceCompatibility = '21' sourceCompatibility = '1.8'
targetCompatibility = '21' targetCompatibility = '1.8'
configurations { configurations {
compileOnlyA
bundleOnly bundleOnly
bundle bundle
hikari
pack pack
proguardPack launch4j
launch4jCJ
bundleOnly.extendsFrom bundle bundleOnly.extendsFrom bundle
api.extendsFrom bundle, pack api.extendsFrom bundle, hikari, pack, launch4jCJ, launch4j
} }
jar { jar {
dependsOn parent.childProjects.Launcher.tasks.assemble dependsOn parent.childProjects.Launcher.tasks.build
from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } } from { configurations.pack.collect { it.isDirectory() ? it : zipTree(it) } }
exclude("module-info.class") from(parent.childProjects.Launcher.tasks.shadowJar.archivePath)
from(parent.childProjects.Launcher.tasks.shadowJar) from(parent.childProjects.Launcher.tasks.genRuntimeJS.archivePath)
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
manifest.attributes("Main-Class": mainClassName, manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName, "Premain-Class": mainAgentName,
"Multi-Release": "true", "Can-Redefine-Classes": "true",
"Automatic-Module-Name": "launchserver" "Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
) )
} }
@ -54,102 +47,143 @@
} }
} }
tasks.register('sourcesJar', Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allJava from sourceSets.main.allJava
archiveClassifier.set('sources') archiveClassifier = 'sources'
} }
tasks.register('javadocJar', Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
archiveClassifier.set('javadoc') archiveClassifier = 'javadoc'
} }
tasks.register('cleanjar', Jar) { task cleanjar(type: Jar, dependsOn: jar) {
dependsOn jar classifier = 'clean'
archiveClassifier.set('clean')
manifest.attributes("Main-Class": mainClassName, manifest.attributes("Main-Class": mainClassName,
"Automatic-Module-Name": "launchserver" "Premain-Class": mainAgentName,
"Can-Redefine-Classes": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true"
) )
from sourceSets.main.output from sourceSets.main.output
} }
dependencies { dependencies {
pack(project(':LauncherAPI')) { pack project(':LauncherAPI')
exclude group: "com.google.code.gson" bundle 'org.ow2.asm:asm-commons:7.3.1'
} bundle 'mysql:mysql-connector-java:8.0.16'
bundle group: 'com.google.code.gson', name: 'gson', version: rootProject['verGson'] bundle 'org.postgresql:postgresql:42.2.6'
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1' bundle 'org.jline:jline:3.13.1'
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi'] bundle 'org.jline:jline-reader:3.13.1'
bundle group: 'org.jline', name: 'jline-native', version: rootProject['verJline'] bundle 'org.jline:jline-terminal:3.13.1'
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline'] bundle 'net.sf.proguard:proguard-base:6.2.0'
bundle group: 'org.jline', name: 'jline-terminal-ffm', version: rootProject['verJline'] bundle 'org.fusesource.jansi:jansi:1.18'
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix'] bundle 'commons-io:commons-io:2.6'
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix'] bundle 'commons-codec:commons-codec:1.12'
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm'] bundle 'org.apache.httpcomponents:httpclient:4.5.10'
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty'] bundle 'io.netty:netty-all:4.1.43.Final'
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty'] bundle 'org.hibernate:hibernate-core:5.4.9.Final'
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64' bundle 'org.bouncycastle:bcpkix-jdk15on:1.61'
//bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
//bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
// Netty
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
//
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
bundle group: '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' bundle 'org.slf4j:slf4j-simple:1.7.25'
bundle('com.zaxxer:HikariCP:6.2.1') { bundle 'org.slf4j:slf4j-api:1.7.25'
hikari 'io.micrometer:micrometer-core:1.0.6'
hikari('com.zaxxer:HikariCP:3.4.1') {
exclude group: 'javassist' exclude group: 'javassist'
exclude group: 'io.micrometer' exclude group: 'io.micrometer'
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
launch4j('net.sf.launch4j:launch4j:3.12') {
exclude group: 'org.apache.ant'
exclude group: 'net.java.abeille'
exclude group: 'foxtrot'
exclude group: 'com.jgoodies'
}
launch4jCJ('net.sf.launch4j:launch4j:3.12:workdir-win32') {
exclude group: '*'
}
launch4jCJ('net.sf.launch4j:launch4j:3.12:workdir-linux') {
exclude group: '*'
}
compileOnlyA 'com.google.guava:guava:26.0-jre'
compileOnlyA 'log4j:log4j:1.2.17' // Do not update (laggy dep).
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.11.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.1'
} }
tasks.register('dumpLibs', Copy) { task hikari(type: Copy) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/hikaricp"
from configurations.hikari
}
task launch4jM(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/launch4j"
from(configurations.launch4jCJ.collect { it.isDirectory() ? it : zipTree(it) })
includeEmptyDirs false
eachFile { FileCopyDetails fcp ->
if (fcp.relativePath.pathString.startsWith("launch4j-")) {
def segments = fcp.relativePath.segments
def pathSegments = segments[1..-1] as String[]
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
fcp.mode = 0755
} else {
fcp.exclude()
}
}
}
task launch4jA(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/libraries/launch4j"
from(configurations.launch4j)
includeEmptyDirs false
eachFile { FileCopyDetails fcp ->
if (fcp.name.startsWith("launch4j")) {
fcp.name = "launch4j.jar"
}
fcp.mode = 0755
}
}
task dumpLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE'
dependsOn tasks.hikari, tasks.launch4jM, tasks.launch4jA
into "$buildDir/libs/libraries" into "$buildDir/libs/libraries"
from configurations.bundleOnly from configurations.bundleOnly
} }
tasks.register('dumpProguard', Copy) { task dumpCompileOnlyLibs(type: Copy) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
into "$buildDir/libs/proguard" into "$buildDir/libs/launcher-libraries-compile"
from configurations.proguardPack from configurations.compileOnlyA
} }
tasks.register('bundle', Zip) { task bundle(type: Zip) {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
archiveFileName = 'LaunchServer.zip' archiveFileName = 'LaunchServer.zip'
destinationDirectory = file("$buildDir") destinationDirectory = file("$buildDir")
from(tasks.dumpLibs.destinationDir) { into 'libraries' } from(tasks.dumpLibs.destinationDir) { into 'libraries' }
from(tasks.jar) from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
from tasks.jar.archivePath
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' } from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
} }
tasks.register('dumpClientLibs', Copy) { task dumpClientLibs(type: Copy) {
dependsOn parent.childProjects.Launcher.tasks.build dependsOn parent.childProjects.Launcher.tasks.build
into "$buildDir/libs/launcher-libraries" into "$buildDir/libs/launcher-libraries"
from parent.childProjects.Launcher.tasks.dumpLibs from parent.childProjects.Launcher.tasks.dumpLibs.destinationDir
} }
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar, tasks.dumpProguard assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
publishing { publishing {
@ -164,7 +198,7 @@
pom { pom {
name = 'GravitLauncher LaunchServer API' name = 'GravitLauncher LaunchServer API'
description = 'GravitLauncher LaunchServer Module API' description = 'GravitLauncher LaunchServer Module API'
url = 'https://gravitlauncher.com' url = 'https://launcher.gravit.pro'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -173,9 +207,9 @@
} }
developers { developers {
developer { developer {
id = 'gravita' id = 'gravit'
name = 'Gravita' name = 'Gravit'
email = 'gravita@gravit.pro' email = 'gravit.min@ya.ru'
} }
developer { developer {
id = 'zaxar163' id = 'zaxar163'
@ -187,7 +221,7 @@
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://gravitlauncher.com/' url = 'https://launcher.gravit.pro/'
} }
} }
} }

View file

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

@ -3,11 +3,12 @@
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager; import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.KeyAgreementManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.utils.command.CommandHandler; import pro.gravit.utils.command.CommandHandler;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
public class LaunchServerBuilder { public class LaunchServerBuilder {
private LaunchServerConfig config; private LaunchServerConfig config;
@ -16,10 +17,10 @@ public class LaunchServerBuilder {
private LaunchServer.LaunchServerEnv env; private LaunchServer.LaunchServerEnv env;
private LaunchServerModulesManager modulesManager; private LaunchServerModulesManager modulesManager;
private LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories(); private LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
private KeyAgreementManager keyAgreementManager; private ECPublicKey publicKey;
private ECPrivateKey privateKey;
private CertificateManager certificateManager; private CertificateManager certificateManager;
private LaunchServer.LaunchServerConfigManager launchServerConfigManager; private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
private Integer shardId;
public LaunchServerBuilder setConfig(LaunchServerConfig config) { public LaunchServerBuilder setConfig(LaunchServerConfig config) {
this.config = config; this.config = config;
@ -56,8 +57,13 @@ public LaunchServerBuilder setDir(Path dir) {
return this; return this;
} }
public LaunchServerBuilder setShardId(Integer shardId) { public LaunchServerBuilder setPublicKey(ECPublicKey publicKey) {
this.shardId = shardId; this.publicKey = publicKey;
return this;
}
public LaunchServerBuilder setPrivateKey(ECPrivateKey privateKey) {
this.privateKey = privateKey;
return this; return this;
} }
@ -67,29 +73,11 @@ public LaunchServerBuilder setLaunchServerConfigManager(LaunchServer.LaunchServe
} }
public LaunchServer build() throws Exception { public LaunchServer build() throws Exception {
//if(updatesDir == null) updatesDir = dir.resolve("updates");
//if(profilesDir == null) profilesDir = dir.resolve("profiles");
directories.collect(); directories.collect();
if (launchServerConfigManager == null) { if (launchServerConfigManager == null) {
launchServerConfigManager = new NullLaunchServerConfigManager(); launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
}
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 @Override
public LaunchServerConfig readConfig() { public LaunchServerConfig readConfig() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -109,5 +97,13 @@ public void writeConfig(LaunchServerConfig config) {
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) { public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
};
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, publicKey, privateKey, commandHandler, certificateManager);
}
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
this.certificateManager = certificateManager;
return this;
} }
} }

View file

@ -1,25 +1,19 @@
package pro.gravit.launchserver; package pro.gravit.launchserver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.core.LauncherTrustManager; import pro.gravit.launcher.hwid.HWIDProvider;
import pro.gravit.launcher.base.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launchserver.auth.handler.AuthHandler;
import pro.gravit.launcher.base.request.auth.AuthRequest; import pro.gravit.launchserver.auth.hwid.HWIDHandler;
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.protect.ProtectHandler;
import pro.gravit.launchserver.auth.provider.AuthProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.components.Component; import pro.gravit.launchserver.components.Component;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.dao.provider.DaoProvider;
import pro.gravit.launchserver.manangers.CertificateManager; import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager; import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
@ -30,65 +24,64 @@
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.launcher.LauncherTrustManager;
import java.io.*; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.List; import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
public class LaunchServerStarter { public class LaunchServerStarter {
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned"); public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
public static final boolean prepareMode = Boolean.getBoolean("launchserver.prepareMode"); public static final boolean inDocker = Boolean.getBoolean("launchserver.dockered");
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
JVMHelper.verifySystemProperties(LaunchServer.class, false); JVMHelper.checkStackTrace(LaunchServerStarter.class);
//LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log")); JVMHelper.verifySystemProperties(LaunchServer.class, true);
LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
LogHelper.printVersion("LaunchServer"); LogHelper.printVersion("LaunchServer");
LogHelper.printLicense("LaunchServer"); LogHelper.printLicense("LaunchServer");
if (!StarterAgent.isAgentStarted()) {
LogHelper.error("StarterAgent is not started!");
LogHelper.error("You should add to JVM options this option: `-javaagent:LaunchServer.jar`");
}
Path dir = IOHelper.WORKING_DIR; Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile; Path configFile, runtimeConfigFile;
try { Path publicKeyFile = dir.resolve("public.key");
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); Path privateKeyFile = dir.resolve("private.key");
ECPublicKey publicKey;
ECPrivateKey privateKey;
Security.addProvider(new 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(); CertificateManager certificateManager = new CertificateManager();
try { try {
certificateManager.readTrustStore(directories.trustStore); certificateManager.readTrustStore(dir.resolve("truststore"));
} catch (CertificateException e) { } catch (CertificateException e) {
throw new IOException(e); throw new IOException(e);
} }
{ {
LauncherTrustManager.CheckClassResult result = certificateManager.checkClass(LaunchServer.class); //LauncherTrustManager.CheckMode mode = (Version.RELEASE == Version.Type.LTS || Version.RELEASE == Version.Type.STABLE) ?
if (result.type == LauncherTrustManager.CheckClassResultType.SUCCESS) { // (allowUnsigned ? LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED) :
logger.info("LaunchServer signed by {}", result.endCertificate.getSubjectX500Principal().getName()); // (allowUnsigned ? LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED);
} else if (result.type == LauncherTrustManager.CheckClassResultType.NOT_SIGNED) { certificateManager.checkClass(LaunchServer.class, LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED);
// None
} else {
if (result.exception != null) {
logger.error(result.exception);
}
logger.warn("LaunchServer signed incorrectly. Status: {}", result.type.name());
}
} }
LaunchServerRuntimeConfig runtimeConfig; LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config; LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION; LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(directories.modules, dir.resolve("config"), certificateManager.trustManager); LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload(); modulesManager.autoload();
modulesManager.initModules(null); modulesManager.initModules(null);
registerAll(); registerAll();
initGson(modulesManager); initGson(modulesManager);
printExperimentalBranch();
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) { if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
configFile = dir.resolve("LaunchServer.conf"); configFile = dir.resolve("LaunchServer.conf");
} else { } else {
@ -105,178 +98,44 @@ public static void main(String[] args) throws Exception {
// JLine2 available // JLine2 available
localCommandHandler = new JLineCommandHandler(); localCommandHandler = new JLineCommandHandler();
logger.info("JLine2 terminal enabled"); LogHelper.info("JLine2 terminal enabled");
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
localCommandHandler = new StdCommandHandler(true); localCommandHandler = new StdCommandHandler(true);
logger.warn("JLine2 isn't in classpath, using std"); LogHelper.warning("JLine2 isn't in classpath, using std");
}
if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) {
LogHelper.info("Reading EC keypair");
publicKey = SecurityHelper.toPublicECKey(IOHelper.read(publicKeyFile));
privateKey = SecurityHelper.toPrivateECKey(IOHelper.read(privateKeyFile));
} else {
LogHelper.info("Generating EC keypair");
KeyPair pair = SecurityHelper.genECKeyPair(new SecureRandom());
publicKey = (ECPublicKey) pair.getPublic();
privateKey = (ECPrivateKey) pair.getPrivate();
// Write key pair list
LogHelper.info("Writing EC keypair list");
IOHelper.write(publicKeyFile, publicKey.getEncoded());
IOHelper.write(privateKeyFile, privateKey.getEncoded());
} }
modulesManager.invokeEvent(new PreConfigPhase()); modulesManager.invokeEvent(new PreConfigPhase());
generateConfigIfNotExists(configFile, localCommandHandler, env); generateConfigIfNotExists(configFile, localCommandHandler, env);
logger.info("Reading LaunchServer config file"); LogHelper.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) { try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class); config = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
} }
if (!Files.exists(runtimeConfigFile)) { if (!Files.exists(runtimeConfigFile)) {
logger.info("Reset LaunchServer runtime config file"); LogHelper.info("Reset LaunchServer runtime config file");
runtimeConfig = new LaunchServerRuntimeConfig(); runtimeConfig = new LaunchServerRuntimeConfig();
runtimeConfig.reset(); runtimeConfig.reset();
} else { } else {
logger.info("Reading LaunchServer runtime config file"); LogHelper.info("Reading LaunchServer runtime config file");
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) { try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
runtimeConfig = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class); runtimeConfig = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
} }
} }
LaunchServer.LaunchServerConfigManager launchServerConfigManager = new BasicLaunchServerConfigManager(configFile, runtimeConfigFile); LaunchServer.LaunchServerConfigManager launchServerConfigManager = new LaunchServer.LaunchServerConfigManager() {
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 @Override
public LaunchServerConfig readConfig() throws IOException { public LaunchServerConfig readConfig() throws IOException {
LaunchServerConfig config1; LaunchServerConfig config1;
@ -297,34 +156,105 @@ public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
@Override @Override
public void writeConfig(LaunchServerConfig config) throws IOException { public void writeConfig(LaunchServerConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream(); try (Writer writer = IOHelper.newWriter(configFile)) {
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) { if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer); Launcher.gsonManager.configGson.toJson(config, writer);
} else { } else {
logger.error("Error writing LaunchServer config file. Gson is null"); LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
} }
} }
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
} }
@Override @Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException { public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream(); try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) { if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer); Launcher.gsonManager.configGson.toJson(config, writer);
} else { } else {
logger.error("Error writing LaunchServer runtime config file. Gson is null"); LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
} }
} }
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(runtimeConfigFile, bytes);
} }
};
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
directories.dir = dir;
if (inDocker) {
Path parentLibraries = StarterAgent.libraries.toAbsolutePath().normalize().getParent();
directories.launcherLibrariesCompileDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIESCOMPILE_NAME);
directories.launcherLibrariesDir = parentLibraries.resolve(LaunchServer.LaunchServerDirectories.LAUNCHERLIBRARIES_NAME);
}
LaunchServer server = new LaunchServerBuilder()
.setDirectories(directories)
.setEnv(env)
.setCommandHandler(localCommandHandler)
.setPrivateKey(privateKey)
.setPublicKey(publicKey)
.setRuntimeConfig(runtimeConfig)
.setConfig(config)
.setModulesManager(modulesManager)
.setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager)
.build();
server.run();
}
public static void initGson(LaunchServerModulesManager modulesManager) {
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
Launcher.gsonManager.initGson();
}
public static void registerAll() {
AuthHandler.registerHandlers();
AuthProvider.registerProviders();
TextureProvider.registerProviders();
HWIDHandler.registerHandlers();
Component.registerComponents();
ProtectHandler.registerHandlers();
WebSocketService.registerResponses();
HWIDProvider.registerHWIDs();
DaoProvider.registerProviders();
AuthRequest.registerProviders();
}
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
if (IOHelper.isFile(configFile))
return;
// Create new config
LogHelper.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 {
System.out.println("LaunchServer address(default: localhost): ");
address = commandHandler.readLine();
System.out.println("LaunchServer projectName: ");
newConfig.setProjectName(commandHandler.readLine());
}
if (address == null || address.isEmpty()) {
LogHelper.error("Address null. Using localhost");
address = "localhost";
}
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
LogHelper.error("ProjectName null. Using MineCraft");
newConfig.projectName = "MineCraft";
}
newConfig.netty.address = "ws://" + address + ":9274/api";
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
// Write LaunchServer config
LogHelper.info("Writing LaunchServer config file");
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
Launcher.gsonManager.configGson.toJson(newConfig, writer);
} }
} }
} }

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

@ -2,7 +2,6 @@
import pro.gravit.utils.command.Command; import pro.gravit.utils.command.Command;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
@ -16,8 +15,4 @@ public interface Reconfigurable {
* Value is a command object * Value is a command object
*/ */
Map<String, Command> getCommands(); Map<String, Command> getCommands();
default Map<String, Command> defaultCommandsMap() {
return new HashMap<>();
}
} }

View file

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

View file

@ -1,17 +1,16 @@
package pro.gravit.launchserver.asm; package pro.gravit.launchserver.asm;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.Closeable; import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile; import java.util.jar.JarFile;
/** /**
@ -19,38 +18,35 @@
* чего угодно. Работает через поиск class-файлов в classpath. * чего угодно. Работает через поиск class-файлов в classpath.
*/ */
public class ClassMetadataReader implements Closeable { public class ClassMetadataReader implements Closeable {
private final Logger logger = LogManager.getLogger(ClassMetadataReader.class); private static class CheckSuperClassVisitor extends ClassVisitor {
String superClassName;
public CheckSuperClassVisitor() {
super(Opcodes.ASM7);
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
superClassName = superName;
}
}
private final List<JarFile> cp; private final List<JarFile> cp;
private final Map<String, Module> moduleClassFinder;
public ClassMetadataReader(List<JarFile> cp) { public ClassMetadataReader(List<JarFile> cp) {
this.cp = cp; this.cp = cp;
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
}
public ClassMetadataReader() {
this.cp = new ArrayList<>();
//var moduleLayer = ClassMetadataReader.class.getModule().getLayer() == null ? ModuleLayer.boot() : ClassMetadataReader.class.getModule().getLayer();
var moduleLayer = ModuleLayer.boot();
moduleClassFinder = collectModulePackages(moduleLayer);
}
private Map<String, Module> collectModulePackages(ModuleLayer layer) {
var map = new HashMap<String, Module>();
for(var m : layer.modules()) {
for(var p : m.getPackages()) {
map.put(p, m);
}
}
return map;
} }
public List<JarFile> getCp() { public List<JarFile> getCp() {
return cp; return cp;
} }
public ClassMetadataReader() {
this.cp = new ArrayList<>();
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor) { public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
new ClassReader(classData).accept(visitor, 0); new ClassReader(classData).accept(visitor, 0);
} }
@ -67,6 +63,7 @@ public void acceptVisitor(String className, ClassVisitor visitor, int flags) thr
acceptVisitor(getClassData(className), visitor, flags); acceptVisitor(getClassData(className), visitor, flags);
} }
public byte[] getClassData(String className) throws IOException { public byte[] getClassData(String className) throws IOException {
for (JarFile f : cp) { for (JarFile f : cp) {
if (f.getEntry(className + ".class") != null) { if (f.getEntry(className + ".class") != null) {
@ -77,42 +74,7 @@ public byte[] getClassData(String className) throws IOException {
return bytes; return bytes;
} }
} }
if(ClassMetadataReader.class.getModule().isNamed()) { return IOHelper.read(IOHelper.getResourceURL(className + ".class"));
String pkg = getClassPackage(className).replace('/', '.');
var module = moduleClassFinder.get(pkg);
if(module != null) {
var cl = module.getClassLoader();
if(cl == null) {
cl = ClassLoader.getPlatformClassLoader();
}
var stream = cl.getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException("Class "+className + ".class");
}
} else {
throw new FileNotFoundException("Package "+pkg);
}
}
var stream = ClassLoader.getSystemClassLoader().getResourceAsStream(className+".class");
if(stream != null) {
try(stream) {
return IOHelper.read(stream);
}
} else {
throw new FileNotFoundException(className + ".class");
}
}
private String getClassPackage(String type) {
int idx = type.lastIndexOf("/");
if(idx <= 0) {
return type;
}
return type.substring(0, idx);
} }
public String getSuperClass(String type) { public String getSuperClass(String type) {
@ -120,7 +82,6 @@ public String getSuperClass(String type) {
try { try {
return getSuperClassASM(type); return getSuperClassASM(type);
} catch (Exception e) { } catch (Exception e) {
logger.warn("getSuperClass: type {} not found ({}: {})", type, e.getClass().getName(), e.getMessage());
return "java/lang/Object"; return "java/lang/Object";
} }
} }
@ -150,19 +111,4 @@ public void close() {
cp.clear(); 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,25 +1,120 @@
package pro.gravit.launchserver.asm; package pro.gravit.launchserver.asm;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import pro.gravit.launcher.core.LauncherInject;
import pro.gravit.launcher.core.LauncherInjectionConstructor;
import pro.gravit.launchserver.binary.BuildContext; import pro.gravit.launchserver.binary.BuildContext;
import pro.gravit.launchserver.binary.tasks.MainBuildTask; import pro.gravit.launchserver.binary.tasks.MainBuildTask;
import pro.gravit.launcher.LauncherInject;
import java.util.*; import pro.gravit.launcher.LauncherInjectionConstructor;
import java.util.concurrent.atomic.AtomicReference;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class InjectClassAcceptor implements MainBuildTask.ASMTransformer { public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
private final Map<String, Object> values;
public InjectClassAcceptor(Map<String, Object> values) {
this.values = values;
}
private static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class, 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); 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_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.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(), 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)); Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
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))
.collect(Collectors.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 = ...;
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<String>(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(
String.format("Invalid annotation with value class %s", 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 (primitiveLDCDescriptors.contains(field.desc) && primitiveLDCClasses.contains(value.getClass())) {
field.value = value;
return;
}
List<FieldInsnNode> putStaticNodes = Arrays.stream(clinitMethod.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)).collect(Collectors.toList());
InsnList setter = serializeValue(value);
if (putStaticNodes.isEmpty()) {
setter.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
Arrays.stream(clinitMethod.instructions.toArray()).filter(node -> node.getOpcode() == Opcodes.RETURN)
.forEach(node -> clinitMethod.instructions.insertBefore(node, setter));
} else {
setter.insert(new InsnNode(Type.getType(field.desc).getSize() == 1 ? Opcodes.POP : Opcodes.POP2));
for (FieldInsnNode fieldInsnNode : putStaticNodes) {
clinitMethod.instructions.insertBefore(fieldInsnNode, setter);
}
}
} else {
if (initMethod == null) {
throw new IllegalArgumentException(String.format("Not found init in target: %s", 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)).collect(Collectors.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 final Map<Class<?>, Serializer<?>> serializers; private static final Map<Class<?>, Serializer<?>> serializers;
static { static {
@ -48,113 +143,24 @@ public class InjectClassAcceptor implements MainBuildTask.ASMTransformer {
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter); 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) { private static Serializer<?> serializerClass(int opcode) {
return (Serializer<Number>) value -> { return new Serializer<Number>() {
@Override
public InsnList serialize(Number value) {
InsnList ret = new InsnList(); InsnList ret = new InsnList();
ret.add(NodeUtils.push(value.intValue())); ret.add(NodeUtils.push(((Number) value).intValue()));
ret.add(new InsnNode(opcode)); ret.add(new InsnNode(opcode));
return ret; return ret;
}
}; };
} }
@FunctionalInterface
private interface Serializer<T> {
InsnList serialize(T value);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static InsnList serializeValue(Object value) { private static InsnList serializeValue(Object value) {
if (value == null) { if (value == null) {
@ -172,28 +178,8 @@ private static InsnList serializeValue(Object value) {
return ((Serializer) serializerEntry.getValue()).serialize(value); return ((Serializer) serializerEntry.getValue()).serialize(value);
} }
} }
throw new UnsupportedOperationException("Serialization of type %s is not supported".formatted(value.getClass())); throw new UnsupportedOperationException(String.format("Serialization of type %s is not supported",
} value.getClass()));
public static boolean isSerializableValue(Object value) {
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> { private static class ListSerializer implements Serializer<List> {
@ -251,4 +237,9 @@ public InsnList serialize(byte[] value) {
return insnList; return insnList;
} }
} }
@Override
public void transform(ClassNode classNode, String className, BuildContext context) {
visit(classNode, values);
}
} }

View file

@ -17,8 +17,6 @@
public final class NodeUtils { public final class NodeUtils {
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
private NodeUtils() { private NodeUtils() {
} }
@ -149,7 +147,10 @@ public static int opcodeEmulation(AbstractInsnNode e) {
break; break;
case INVOKEVIRTUAL: case INVOKEVIRTUAL:
case INVOKESPECIAL: case INVOKESPECIAL:
case INVOKEINTERFACE, INVOKESTATIC: case INVOKEINTERFACE:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break;
case INVOKESTATIC:
stackSize += doMethodEmulation(((MethodInsnNode) e).desc); stackSize += doMethodEmulation(((MethodInsnNode) e).desc);
break; break;
case INVOKEDYNAMIC: case INVOKEDYNAMIC:
@ -185,6 +186,8 @@ public static InsnList getSafeStringInsnList(String string) {
return insnList; return insnList;
} }
public static final int MAX_SAFE_BYTE_COUNT = 65535 - Byte.MAX_VALUE;
public static String[] splitUtf8ToChunks(String text, int maxBytes) { public static String[] splitUtf8ToChunks(String text, int maxBytes) {
List<String> parts = new ArrayList<>(); List<String> parts = new ArrayList<>();

View file

@ -1,14 +1,8 @@
package pro.gravit.launchserver.auth; package pro.gravit.launchserver.auth;
import pro.gravit.launcher.base.events.request.AuthRequestEvent;
import java.io.IOException; import java.io.IOException;
import java.io.Serial;
import java.util.List;
import java.util.stream.Collectors;
public final class AuthException extends IOException { public final class AuthException extends IOException {
@Serial
private static final long serialVersionUID = -2586107832847245863L; private static final long serialVersionUID = -2586107832847245863L;
@ -16,28 +10,6 @@ public AuthException(String message) {
super(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 @Override
public String toString() { public String toString() {
return getMessage(); return getMessage();

View file

@ -1,113 +1,76 @@
package pro.gravit.launchserver.auth; 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.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.handler.AuthHandler;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.hwid.HWIDHandler;
import pro.gravit.launchserver.auth.provider.AuthProvider;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
public final class AuthProviderPair { public class AuthProviderPair {
private transient final Logger logger = LogManager.getLogger(); public AuthProvider provider;
public boolean isDefault = true; public AuthHandler handler;
public AuthCoreProvider core;
public TextureProvider textureProvider; public TextureProvider textureProvider;
public Map<String, MixProvider> mixes; public HWIDHandler hwid;
public Map<String, String> links; public Map<String, String> links;
public transient String name; public transient String name;
public transient Set<String> features;
public String displayName; public String displayName;
public boolean visible = true; public final boolean isDefault = true;
public AuthProviderPair() { public AuthProviderPair(AuthProvider provider, AuthHandler handler, TextureProvider textureProvider, HWIDHandler hwid) {
} this.provider = provider;
this.handler = handler;
public AuthProviderPair(AuthCoreProvider core, TextureProvider textureProvider) {
this.core = core;
this.textureProvider = textureProvider; this.textureProvider = textureProvider;
} this.hwid = hwid;
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) { public void init(LaunchServer srv, String name) {
this.name = name; this.name = name;
if (links != null) link(srv); if(links != null) link(srv);
core.init(srv, this); if(provider == null) throw new NullPointerException(String.format("Auth %s provider null", name));
features = new HashSet<>(); if(handler == null) throw new NullPointerException(String.format("Auth %s handler null", name));
getFeatures(core.getClass(), features); if(textureProvider == null) throw new NullPointerException(String.format("Auth %s textureProvider null", name));
if(mixes != null) { if(hwid == null) throw new NullPointerException(String.format("Auth %s hwid null", name));
for(var m : mixes.values()) { provider.init(srv);
m.init(srv, core); handler.init(srv);
getFeatures(m.getClass(), features); hwid.init();
} }
} public void link(LaunchServer srv)
} {
links.forEach((k,v) -> {
public void link(LaunchServer srv) {
links.forEach((k, v) -> {
AuthProviderPair pair = srv.config.getAuthProviderPair(v); AuthProviderPair pair = srv.config.getAuthProviderPair(v);
if (pair == null) { if(pair == null)
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v)); {
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
} }
if ("core".equals(k)) { if("provider".equals(k))
if (pair.core == null) {
throw new NullPointerException("Auth %s link failed. %s.core is null".formatted(name, v)); if(pair.provider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.provider is null", name, v));
core = pair.core; provider = pair.provider;
}
else if("handler".equals(k))
{
if(pair.handler == null) throw new NullPointerException(String.format("Auth %s link failed. %s.handler is null", name, v));
handler = pair.handler;
}
else if("textureProvider".equals(k))
{
if(pair.textureProvider == null) throw new NullPointerException(String.format("Auth %s link failed. %s.textureProvider is null", name, v));
textureProvider = pair.textureProvider;
}
else if("hwid".equals(k))
{
if(pair.hwid == null) throw new NullPointerException(String.format("Auth %s link failed. %s.hwid is null", name, v));
hwid = pair.hwid;
} }
}); });
} }
public void close() throws IOException { public void close() throws IOException {
core.close(); provider.close();
if (textureProvider != null) { handler.close();
textureProvider.close(); 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

@ -3,17 +3,14 @@
import com.mysql.cj.jdbc.MysqlDataSource; import com.mysql.cj.jdbc.MysqlDataSource;
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.apache.logging.log4j.LogManager; import pro.gravit.utils.helper.LogHelper;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.helper.VerifyHelper;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES; public final class MySQLSourceConfig implements AutoCloseable {
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
@ -24,7 +21,6 @@ public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
// Instance // Instance
private transient final String poolName; private transient final String poolName;
private transient final Logger logger = LogManager.getLogger();
// Config // Config
private String address; private String address;
@ -34,9 +30,8 @@ public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
private String username; private String username;
private String password; private String password;
private String database; private String database;
private String timezone; private String timeZone;
private long hikariMaxLifetime = MINUTES.toMillis(30); private boolean enableHikari;
private boolean useHikari;
// Cache // Cache
private transient DataSource source; private transient DataSource source;
@ -47,21 +42,6 @@ public MySQLSourceConfig(String poolName) {
this.poolName = 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 @Override
public synchronized void close() { public synchronized void close() {
if (hikari) if (hikari)
@ -96,28 +76,24 @@ public synchronized Connection getConnection() throws SQLException {
mysqlSource.setPassword(password); mysqlSource.setPassword(password);
mysqlSource.setDatabaseName(database); mysqlSource.setDatabaseName(database);
mysqlSource.setTcpNoDelay(true); mysqlSource.setTcpNoDelay(true);
if (timezone != null) mysqlSource.setServerTimezone(timezone); if (timeZone != null) mysqlSource.setServerTimezone(timeZone);
hikari = false; hikari = false;
// Try using HikariCP // Try using HikariCP
source = mysqlSource; source = mysqlSource;
if (useHikari) { if (enableHikari) {
try { try {
Class.forName("com.zaxxer.hikari.HikariDataSource"); Class.forName("com.zaxxer.hikari.HikariDataSource");
hikari = true; // Used for shutdown. Not instanceof because of possible classpath error hikari = true; // Used for shutdown. Not instanceof because of possible classpath error
HikariConfig hikariConfig = new HikariConfig(); HikariConfig cfg = new HikariConfig();
hikariConfig.setDataSource(mysqlSource); cfg.setDataSource(mysqlSource);
hikariConfig.setPoolName(poolName); cfg.setPoolName(poolName);
hikariConfig.setMinimumIdle(1); cfg.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE);
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setConnectionTimeout(1000);
hikariConfig.setLeakDetectionThreshold(2000);
hikariConfig.setMaxLifetime(hikariMaxLifetime);
// Set HikariCP pool // Set HikariCP pool
// Replace source with hds // Replace source with hds
source = new HikariDataSource(hikariConfig); source = new HikariDataSource(cfg);
LogHelper.warning("HikariCP pooling enabled for '%s'", poolName);
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
logger.debug("HikariCP isn't in classpath for '{}'", poolName); LogHelper.debug("HikariCP isn't in classpath for '%s'", poolName);
} }
} }

View file

@ -1,40 +1,35 @@
package pro.gravit.launchserver.auth; package pro.gravit.launchserver.auth;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.postgresql.ds.PGSimpleDataSource; import org.postgresql.ds.PGSimpleDataSource;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.VerifyHelper; import pro.gravit.utils.helper.VerifyHelper;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import static java.util.concurrent.TimeUnit.MINUTES; public final class PostgreSQLSourceConfig implements AutoCloseable {
import static java.util.concurrent.TimeUnit.SECONDS;
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000"); VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt( private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.maxPoolSize", Integer.toString(3))), Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.maxPoolSize", Integer.toString(3))),
VerifyHelper.POSITIVE, "launcher.postgresql.maxPoolSize can't be <= 0"); VerifyHelper.POSITIVE, "launcher.postgresql.maxPoolSize can't be <= 0");
private transient final Logger logger = LogManager.getLogger();
// Instance // Instance
private String poolName; private String poolName;
// Config // Config
private String[] addresses; private String address;
private int[] ports; private int port;
private String username; private String username;
private String password; private String password;
private String database; private String database;
private long hikariMaxLifetime = MINUTES.toMillis(30); // 30 minutes
// Cache // Cache
private transient DataSource source; private DataSource source;
private transient boolean hikari; private boolean hikari;
@Override @Override
public synchronized void close() { public synchronized void close() {
@ -48,8 +43,8 @@ public synchronized Connection getConnection() throws SQLException {
PGSimpleDataSource postgresqlSource = new PGSimpleDataSource(); PGSimpleDataSource postgresqlSource = new PGSimpleDataSource();
// Set credentials // Set credentials
postgresqlSource.setServerNames(addresses); postgresqlSource.setServerName(address);
postgresqlSource.setPortNumbers(ports); postgresqlSource.setPortNumber(port);
postgresqlSource.setUser(username); postgresqlSource.setUser(username);
postgresqlSource.setPassword(password); postgresqlSource.setPassword(password);
postgresqlSource.setDatabaseName(database); postgresqlSource.setDatabaseName(database);
@ -70,14 +65,13 @@ public synchronized Connection getConnection() throws SQLException {
hikariSource.setPoolName(poolName); hikariSource.setPoolName(poolName);
hikariSource.setMinimumIdle(0); hikariSource.setMinimumIdle(0);
hikariSource.setMaximumPoolSize(MAX_POOL_SIZE); hikariSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariSource.setIdleTimeout(SECONDS.toMillis(TIMEOUT)); hikariSource.setIdleTimeout(TIMEOUT * 1000L);
hikariSource.setMaxLifetime(hikariMaxLifetime);
// Replace source with hds // Replace source with hds
source = hikariSource; source = hikariSource;
logger.info("HikariCP pooling enabled for '{}'", poolName); LogHelper.info("HikariCP pooling enabled for '%s'", poolName);
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
logger.warn("HikariCP isn't in classpath for '{}'", poolName); LogHelper.warning("HikariCP isn't in classpath for '%s'", poolName);
} }
} }
return source.getConnection(); 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

@ -0,0 +1,79 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.provider.AuthProviderResult;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.UUID;
public abstract class AuthHandler implements AutoCloseable {
public static final ProviderMap<AuthHandler> providers = new ProviderMap<>("AuthHandler");
private static boolean registredHandl = false;
public static UUID authError(String message) throws AuthException {
throw new AuthException(message);
}
public static void registerHandlers() {
if (!registredHandl) {
providers.register("null", NullAuthHandler.class);
providers.register("json", JsonAuthHandler.class);
providers.register("memory", MemoryAuthHandler.class);
providers.register("mysql", MySQLAuthHandler.class);
providers.register("postgresql", PostgreSQLAuthHandler.class);
providers.register("request", RequestAuthHandler.class);
providers.register("hibernate", HibernateAuthHandler.class);
registredHandl = true;
}
}
protected transient LaunchServer srv;
/**
* Returns the UUID associated with the account
*
* @param authResult {@link pro.gravit.launchserver.auth.provider.AuthProvider} result
* @return User UUID
* @throws IOException Internal Script Error
*/
public abstract UUID auth(AuthProviderResult authResult) throws IOException;
/**
* Validates serverID
*
* @param username user name
* @param serverID serverID to check
* @return user UUID
* @throws IOException Internal Script Error
*/
public abstract UUID checkServer(String username, String serverID) throws IOException;
@Override
public abstract void close() throws IOException;
/**
* Checks assessToken for validity and saves serverID if successful
*
* @param username user name
* @param accessToken assessToken to check
* @param serverID serverID to save
* @return true - allow, false - deny
* @throws IOException Internal Script Error
*/
public abstract boolean joinServer(String username, String accessToken, String serverID) throws IOException;
public abstract UUID usernameToUUID(String username) throws IOException;
public abstract String uuidToUsername(UUID uuid) throws IOException;
public void init(LaunchServer srv) {
this.srv = srv;
}
}

View file

@ -0,0 +1,204 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.NeedGarbageCollection;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.auth.provider.AuthProviderResult;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.*;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
public abstract class CachedAuthHandler extends AuthHandler implements NeedGarbageCollection, Reconfigurable {
public static final class Entry {
public final UUID uuid;
private String username;
private String accessToken;
private String serverID;
public Entry(UUID uuid, String username, String accessToken, String serverID) {
this.uuid = Objects.requireNonNull(uuid, "uuid");
this.username = Objects.requireNonNull(username, "username");
this.accessToken = accessToken == null ? null : SecurityHelper.verifyToken(accessToken);
this.serverID = serverID == null ? null : VerifyHelper.verifyServerID(serverID);
}
}
protected static class EntryAndUsername {
public Map<UUID, CachedAuthHandler.Entry> entryCache;
public Map<String, UUID> usernameCache;
}
@Override
public Map<String, Command> getCommands() {
Map<String, Command> commands = new HashMap<>();
commands.put("clear", new SubCommand() {
@Override
public void invoke(String... args) {
long entryCacheSize = entryCache.size();
long usernamesCacheSize = usernamesCache.size();
entryCache.clear();
usernamesCache.clear();
LogHelper.info("Cleared cache: %d Entry %d Usernames", entryCacheSize, usernamesCacheSize);
}
});
commands.put("load", new SubCommand() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
LogHelper.info("CachedAuthHandler read from %s", args[0]);
int size_entry;
int size_username;
try (Reader reader = IOHelper.newReader(Paths.get(args[1]))) {
EntryAndUsername entryAndUsername = Launcher.gsonManager.configGson.fromJson(reader, EntryAndUsername.class);
size_entry = entryAndUsername.entryCache.size();
size_username = entryAndUsername.usernameCache.size();
loadEntryCache(entryAndUsername.entryCache);
loadUsernameCache(entryAndUsername.usernameCache);
}
LogHelper.subInfo("Readed %d entryCache %d usernameCache", size_entry, size_username);
}
});
commands.put("unload", new SubCommand() {
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
LogHelper.info("CachedAuthHandler write to %s", args[1]);
Map<UUID, CachedAuthHandler.Entry> entryCache = getEntryCache();
Map<String, UUID> usernamesCache = getUsernamesCache();
EntryAndUsername serializable = new EntryAndUsername();
serializable.entryCache = entryCache;
serializable.usernameCache = usernamesCache;
try (Writer writer = IOHelper.newWriter(Paths.get(args[1]))) {
Launcher.gsonManager.configGson.toJson(serializable, writer);
}
LogHelper.subInfo("Write %d entryCache, %d usernameCache", entryCache.size(), usernamesCache.size());
}
});
return commands;
}
private transient final Map<UUID, Entry> entryCache = new HashMap<>(1024);
private transient final Map<String, UUID> usernamesCache = new HashMap<>(1024);
protected void addEntry(Entry entry) {
Entry previous = entryCache.put(entry.uuid, entry);
if (previous != null)
usernamesCache.remove(CommonHelper.low(previous.username));
usernamesCache.put(CommonHelper.low(entry.username), entry.uuid);
}
@Override
public final synchronized UUID auth(AuthProviderResult result) throws IOException {
Entry entry = getEntry(result.username);
if (entry == null || !updateAuth(entry.uuid, entry.username, result.accessToken))
return authError(String.format("UUID is null for username '%s'", result.username));
// Update cached access token (and username case)
entry.username = result.username;
entry.accessToken = result.accessToken;
entry.serverID = null;
return entry.uuid;
}
@Override
public synchronized UUID checkServer(String username, String serverID) throws IOException {
Entry entry = getEntry(username);
return entry != null && username.equals(entry.username) &&
serverID.equals(entry.serverID) ? entry.uuid : null;
}
protected abstract Entry fetchEntry(String username) throws IOException;
protected abstract Entry fetchEntry(UUID uuid) throws IOException;
private Entry getEntry(String username) throws IOException {
UUID uuid = usernamesCache.get(CommonHelper.low(username));
if (uuid != null)
return getEntry(uuid);
// Fetch entry by username
Entry entry = fetchEntry(username);
if (entry != null)
addEntry(entry);
// Return what we got
return entry;
}
private Entry getEntry(UUID uuid) throws IOException {
Entry entry = entryCache.get(uuid);
if (entry == null) {
entry = fetchEntry(uuid);
if (entry != null)
addEntry(entry);
}
return entry;
}
@Override
public synchronized boolean joinServer(String username, String accessToken, String serverID) throws IOException {
Entry entry = getEntry(username);
if (entry == null || !username.equals(entry.username) || !accessToken.equals(entry.accessToken) ||
!updateServerID(entry.uuid, serverID))
return false; // Account doesn't exist or invalid access token
// Update cached server ID
entry.serverID = serverID;
return true;
}
public synchronized void garbageCollection() {
entryCache.clear();
usernamesCache.clear();
}
public Map<UUID, Entry> getEntryCache() {
return entryCache;
}
public Map<String, UUID> getUsernamesCache() {
return usernamesCache;
}
public void loadEntryCache(Map<UUID, Entry> map) {
entryCache.putAll(map);
}
public void loadUsernameCache(Map<String, UUID> map) {
usernamesCache.putAll(map);
}
protected abstract boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException;
protected abstract boolean updateServerID(UUID uuid, String serverID) throws IOException;
@Override
public final synchronized UUID usernameToUUID(String username) throws IOException {
Entry entry = getEntry(username);
return entry == null ? null : entry.uuid;
}
@Override
public final synchronized String uuidToUsername(UUID uuid) throws IOException {
Entry entry = getEntry(uuid);
return entry == null ? null : entry.username;
}
}

View file

@ -0,0 +1,42 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launchserver.dao.User;
import java.util.UUID;
public class HibernateAuthHandler extends CachedAuthHandler {
@Override
protected Entry fetchEntry(String username) {
User user = srv.config.dao.userDAO.findByUsername(username);
if (user == null) return null;
return new Entry(user.getUuid(), username, user.getAccessToken(), user.getServerID());
}
@Override
protected Entry fetchEntry(UUID uuid) {
User user = srv.config.dao.userDAO.findByUUID(uuid);
if (user == null) return null;
return new Entry(user.getUuid(), user.getUsername(), user.getAccessToken(), user.getServerID());
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) {
User user = srv.config.dao.userDAO.findByUUID(uuid);
user.setAccessToken(accessToken);
srv.config.dao.userDAO.update(user);
return true;
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) {
User user = srv.config.dao.userDAO.findByUUID(uuid);
user.setServerID(serverID);
srv.config.dao.userDAO.update(user);
return true;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,81 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.HTTPRequest;
import java.io.IOException;
import java.net.URL;
import java.util.UUID;
public class JsonAuthHandler extends CachedAuthHandler {
public URL getUrl;
public URL updateAuthUrl;
public URL updateServerIdUrl;
public static class EntryRequestByUsername {
public final String username;
public EntryRequestByUsername(String username) {
this.username = username;
}
}
public static class EntryRequestByUUID {
public final UUID uuid;
public EntryRequestByUUID(UUID uuid) {
this.uuid = uuid;
}
}
public static class UpdateAuthRequest {
public final UUID uuid;
public final String username;
public final String accessToken;
public UpdateAuthRequest(UUID uuid, String username, String accessToken) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
}
}
public static class UpdateServerIDRequest {
public final UUID uuid;
public final String serverID;
public UpdateServerIDRequest(UUID uuid, String serverID) {
this.uuid = uuid;
this.serverID = serverID;
}
}
public static class SuccessResponse {
public boolean success;
}
@Override
protected Entry fetchEntry(String username) throws IOException {
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUsername(username)), getUrl), Entry.class);
}
@Override
protected Entry fetchEntry(UUID uuid) throws IOException {
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUUID(uuid)), getUrl), Entry.class);
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateAuthRequest(uuid, username, accessToken)), updateAuthUrl), SuccessResponse.class).success;
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateServerIDRequest(uuid, serverID)), updateServerIdUrl), SuccessResponse.class).success;
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,54 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.UUID;
public final class MemoryAuthHandler extends CachedAuthHandler {
private static String toUsername(UUID uuid) {
byte[] bytes = ByteBuffer.allocate(16).
putLong(uuid.getMostSignificantBits()).
putLong(uuid.getLeastSignificantBits()).array();
// Find username end
int length = 0;
while (length < bytes.length && bytes[length] != 0)
length++;
// Decode and verify
return VerifyHelper.verifyUsername(new String(bytes, 0, length, IOHelper.ASCII_CHARSET));
}
private static UUID toUUID(String username) {
ByteBuffer buffer = ByteBuffer.wrap(Arrays.copyOf(IOHelper.encodeASCII(username), 16));
return new UUID(buffer.getLong(), buffer.getLong()); // MOST, LEAST
}
@Override
public void close() {
// Do nothing
}
@Override
protected Entry fetchEntry(String username) {
return new Entry(toUUID(username), username, null, null);
}
@Override
protected Entry fetchEntry(UUID uuid) {
return new Entry(uuid, toUsername(uuid), null, null);
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) {
return true; // Do nothing
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) {
return true; // Do nothing
}
}

View file

@ -0,0 +1,108 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public final class MySQLAuthHandler extends CachedAuthHandler {
private MySQLSourceConfig mySQLHolder;
private String uuidColumn;
private String usernameColumn;
private String accessTokenColumn;
private String serverIDColumn;
private String table;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
@Override
public void init(LaunchServer srv) {
super.init(srv);
//Verify
if (mySQLHolder == null) LogHelper.error("[Verify][AuthHandler] mySQLHolder cannot be null");
if (uuidColumn == null) LogHelper.error("[Verify][AuthHandler] uuidColumn cannot be null");
if (usernameColumn == null) LogHelper.error("[Verify][AuthHandler] usernameColumn cannot be null");
if (accessTokenColumn == null) LogHelper.error("[Verify][AuthHandler] accessTokenColumn cannot be null");
if (serverIDColumn == null) LogHelper.error("[Verify][AuthHandler] serverIDColumn cannot be null");
if (table == null) LogHelper.error("[Verify][AuthHandler] table cannot be null");
// Prepare SQL queries
queryByUUIDSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, uuidColumn);
queryByUsernameSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, usernameColumn);
updateAuthSQL = String.format("UPDATE %s SET %s=?, %s=?, %s=NULL WHERE %s=? LIMIT 1",
table, usernameColumn, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=? LIMIT 1",
table, serverIDColumn, uuidColumn);
}
@Override
public void close() {
mySQLHolder.close();
}
private Entry constructEntry(ResultSet set) throws SQLException {
return set.next() ? new Entry(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn)) : null;
}
@Override
protected Entry fetchEntry(String username) throws IOException {
return query(queryByUsernameSQL, username);
}
@Override
protected Entry fetchEntry(UUID uuid) throws IOException {
return query(queryByUUIDSQL, uuid.toString());
}
private Entry query(String sql, String value) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructEntry(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, username); // Username case
s.setString(2, accessToken);
s.setString(3, uuid.toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, uuid.toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
}

View file

@ -0,0 +1,53 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launchserver.auth.provider.AuthProviderResult;
import pro.gravit.utils.helper.VerifyHelper;
import java.io.IOException;
import java.util.Objects;
import java.util.UUID;
public final class NullAuthHandler extends AuthHandler {
private volatile AuthHandler handler;
@Override
public UUID auth(AuthProviderResult authResult) throws IOException {
return getHandler().auth(authResult);
}
@Override
public UUID checkServer(String username, String serverID) throws IOException {
return getHandler().checkServer(username, serverID);
}
@Override
public void close() throws IOException {
AuthHandler handler = this.handler;
if (handler != null)
handler.close();
}
private AuthHandler getHandler() {
return VerifyHelper.verify(handler, Objects::nonNull, "Backend auth handler wasn't set");
}
@Override
public boolean joinServer(String username, String accessToken, String serverID) throws IOException {
return getHandler().joinServer(username, accessToken, serverID);
}
public void setBackend(AuthHandler handler) {
this.handler = handler;
}
@Override
public UUID usernameToUUID(String username) throws IOException {
return getHandler().usernameToUUID(username);
}
@Override
public String uuidToUsername(UUID uuid) throws IOException {
return getHandler().uuidToUsername(uuid);
}
}

View file

@ -0,0 +1,118 @@
package pro.gravit.launchserver.auth.handler;
import org.postgresql.util.PGobject;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public final class PostgreSQLAuthHandler extends CachedAuthHandler {
private PostgreSQLSourceConfig postgreSQLHolder;
private String uuidColumn;
private String usernameColumn;
private String accessTokenColumn;
private String serverIDColumn;
private String queryByUUIDSQL;
private String queryByUsernameSQL;
private String updateAuthSQL;
private String updateServerIDSQL;
@Override
public void close() {
postgreSQLHolder.close();
}
private Entry constructEntry(ResultSet set) throws SQLException {
return set.next() ? new Entry(UUID.fromString(set.getString(uuidColumn)),
set.getString(usernameColumn), set.getString(accessTokenColumn), set.getString(serverIDColumn)) : null;
}
@Override
protected Entry fetchEntry(String username) throws IOException {
return query(queryByUsernameSQL, username);
}
@Override
protected Entry fetchEntry(UUID uuid) throws IOException {
return query(queryByUUIDSQL, uuid);
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
try (Connection c = postgreSQLHolder.getConnection();
PreparedStatement s = c.prepareStatement(updateAuthSQL)) {
s.setString(1, username); // Username case
s.setString(2, accessToken);
PGobject uuidObject = new PGobject();
uuidObject.setType("uuid");
uuidObject.setValue(uuid.toString());
s.setObject(3, uuidObject);
// Execute update
s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
try (Connection c = postgreSQLHolder.getConnection();
PreparedStatement s = c.prepareStatement(updateServerIDSQL)) {
s.setString(1, serverID);
PGobject uuidObject = new PGobject();
uuidObject.setType("uuid");
uuidObject.setValue(uuid.toString());
s.setObject(2, uuidObject);
// Execute update
s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
private Entry query(String sql, String value) throws IOException {
try (Connection c = postgreSQLHolder.getConnection();
PreparedStatement s = c.prepareStatement(sql)) {
s.setString(1, value);
// Execute query
s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructEntry(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
private Entry query(String sql, UUID value) throws IOException {
try (Connection c = postgreSQLHolder.getConnection();
PreparedStatement s = c.prepareStatement(sql)) {
PGobject uuidObject = new PGobject();
uuidObject.setType("uuid");
uuidObject.setValue(value.toString());
s.setObject(1, uuidObject);
// Execute query
s.setQueryTimeout(PostgreSQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructEntry(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
}

View file

@ -0,0 +1,91 @@
package pro.gravit.launchserver.auth.handler;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.net.URL;
import java.util.UUID;
public final class RequestAuthHandler extends CachedAuthHandler {
private String usernameFetch;
private String uuidFetch;
private String updateAuth;
private String updateServerID;
private final String splitSymbol = ":";
private final String goodResponse = "OK";
@Override
public void init(LaunchServer srv) {
super.init(srv);
if (usernameFetch == null)
LogHelper.error("[Verify][AuthHandler] usernameFetch cannot be null");
if (uuidFetch == null)
LogHelper.error("[Verify][AuthHandler] uuidFetch cannot be null");
if (updateAuth == null)
LogHelper.error("[Verify][AuthHandler] updateAuth cannot be null");
if (updateServerID == null)
LogHelper.error("[Verify][AuthHandler] updateServerID cannot be null");
}
@Override
protected Entry fetchEntry(UUID uuid) throws IOException {
String response = IOHelper.request(new URL(CommonHelper.replace(uuidFetch, "uuid", IOHelper.urlEncode(uuid.toString()))));
String[] parts = response.split(splitSymbol);
String username = parts[0];
String accessToken = parts[1];
String serverID = parts[2];
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("[AuthHandler] Got username: " + username);
LogHelper.debug("[AuthHandler] Got accessToken: " + accessToken);
LogHelper.debug("[AuthHandler] Got serverID: " + serverID);
LogHelper.debug("[AuthHandler] Got UUID: " + uuid);
}
return new Entry(uuid, username, accessToken, serverID);
}
@Override
protected Entry fetchEntry(String username) throws IOException {
String response = IOHelper.request(new URL(CommonHelper.replace(usernameFetch, "user", IOHelper.urlEncode(username))));
String[] parts = response.split(splitSymbol);
UUID uuid = UUID.fromString(parts[0]);
String accessToken = parts[1];
String serverID = parts[2];
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("[AuthHandler] Got username: " + username);
LogHelper.debug("[AuthHandler] Got accessToken: " + accessToken);
LogHelper.debug("[AuthHandler] Got serverID: " + serverID);
LogHelper.debug("[AuthHandler] Got UUID: " + uuid);
}
return new Entry(uuid, username, accessToken, serverID);
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
String response = IOHelper.request(new URL(CommonHelper.replace(updateAuth, "user", IOHelper.urlEncode(username), "uuid", IOHelper.urlEncode(uuid.toString()), "token", IOHelper.urlEncode(accessToken))));
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("[AuthHandler] Set accessToken: " + accessToken);
LogHelper.debug("[AuthHandler] Set UUID: " + uuid);
LogHelper.debug("[AuthHandler] For this username: " + username);
}
return goodResponse.equals(response);
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
String response = IOHelper.request(new URL(CommonHelper.replace(updateAuth, "serverid", IOHelper.urlEncode(serverID), "uuid", IOHelper.urlEncode(uuid.toString()))));
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("[AuthHandler] Set serverID: " + serverID);
LogHelper.debug("[AuthHandler] For this UUID: " + uuid);
}
return goodResponse.equals(response);
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,40 @@
package pro.gravit.launchserver.auth.hwid;
import pro.gravit.launcher.hwid.HWID;
import java.util.ArrayList;
import java.util.List;
public class AcceptHWIDHandler extends HWIDHandler {
@Override
public void ban(List<HWID> hwid) {
//SKIP
}
@Override
public void check0(HWID hwid, String username) {
//SKIP
}
@Override
public void close() {
//SKIP
}
@Override
public void init() {
}
@Override
public List<HWID> getHwid(String username) {
return new ArrayList<>();
}
@Override
public void unban(List<HWID> hwid) {
//SKIP
}
}

View file

@ -0,0 +1,23 @@
package pro.gravit.launchserver.auth.hwid;
public class HWIDException extends Exception {
/**
*
*/
private static final long serialVersionUID = -5307315891121889972L;
public HWIDException() {
}
public HWIDException(String s) {
super(s);
}
public HWIDException(String s, Throwable throwable) {
super(s, throwable);
}
public HWIDException(Throwable throwable) {
super(throwable);
}
}

View file

@ -0,0 +1,80 @@
package pro.gravit.launchserver.auth.hwid;
import pro.gravit.launcher.hwid.HWID;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.LogHelper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class HWIDHandler implements AutoCloseable, Reconfigurable {
public static final ProviderMap<HWIDHandler> providers = new ProviderMap<>("HWIDHandler");
private static boolean registredHandl = false;
public static void registerHandlers() {
if (!registredHandl) {
providers.register("accept", AcceptHWIDHandler.class);
providers.register("mysql", MysqlHWIDHandler.class);
providers.register("json", JsonHWIDHandler.class);
providers.register("jsonfile", JsonFileHWIDHandler.class);
providers.register("memory", MemoryHWIDHandler.class);
registredHandl = true;
}
}
@Override
public Map<String, Command> getCommands() {
Map<String, Command> commands = new HashMap<>();
commands.put("ban", new SubCommand() {
@Override
public void invoke(String... args) throws Exception {
List<HWID> target = getHwid(args[0]);
ban(target);
}
});
commands.put("unban", new SubCommand() {
@Override
public void invoke(String... args) throws Exception {
List<HWID> target = getHwid(args[0]);
unban(target);
}
});
commands.put("gethwid", new SubCommand() {
@Override
public void invoke(String... args) throws Exception {
List<HWID> target = getHwid(args[0]);
for (HWID hwid : target) {
if (hwid == null) {
LogHelper.error("[%s] HWID: null", args[0]);
continue;
}
LogHelper.info("[%s] HWID: %s", args[0], hwid.toString());
}
}
});
return commands;
}
public abstract void ban(List<HWID> hwid) throws HWIDException;
public void check(HWID hwid, String username) throws HWIDException {
if (hwid == null || hwid.isNull()) return;
check0(hwid, username);
}
public abstract void check0(HWID hwid, String username) throws HWIDException;
@Override
public abstract void close() throws Exception;
public abstract void init();
public abstract List<HWID> getHwid(String username) throws HWIDException;
public abstract void unban(List<HWID> hwid) throws HWIDException;
}

View file

@ -0,0 +1,113 @@
package pro.gravit.launchserver.auth.hwid;
import com.google.gson.reflect.TypeToken;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.hwid.HWID;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class JsonFileHWIDHandler extends HWIDHandler {
public static class Entry {
public final HWID hwid;
public String username;
public boolean isBanned = false;
public Entry(HWID hwid) {
this.hwid = hwid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(hwid, entry.hwid);
}
@Override
public int hashCode() {
return Objects.hash(hwid);
}
}
public final String filename = "hwids.json";
public transient LinkedList<Entry> list = new LinkedList<>();
public final String banMessage = "You banned";
@Override
public void ban(List<HWID> hwid) {
for (Entry e : list) {
for (HWID banHWID : hwid) {
if (e.hwid.equals(banHWID)) {
e.isBanned = true;
break;
}
}
}
}
@Override
public void init() {
Path path = Paths.get(filename);
Type type = new TypeToken<LinkedList<Entry>>() {
}.getType();
try (Reader reader = IOHelper.newReader(path)) {
list = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}
}
@Override
public void check0(HWID hwid, String username) throws HWIDException {
boolean isOne = false;
for (Entry e : list) {
if (e.hwid.equals(hwid)) {
isOne = true;
if (e.isBanned) throw new HWIDException(banMessage);
}
}
if (!isOne) {
list.add(new Entry(hwid));
}
}
@Override
public void close() throws Exception {
Path path = Paths.get(filename);
try (Writer writer = IOHelper.newWriter(path)) {
Launcher.gsonManager.configGson.toJson(list, writer);
}
}
@Override
public List<HWID> getHwid(String username) {
LinkedList<HWID> hwids = new LinkedList<>();
for (Entry e : list) {
if (e.username.equals(username)) hwids.add(e.hwid);
}
return hwids;
}
@Override
public void unban(List<HWID> hwid) {
for (Entry e : list) {
for (HWID banHWID : hwid) {
if (e.hwid.equals(banHWID)) {
e.isBanned = false;
break;
}
}
}
}
}

View file

@ -0,0 +1,148 @@
package pro.gravit.launchserver.auth.hwid;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import pro.gravit.launcher.hwid.HWID;
import pro.gravit.launcher.hwid.OshiHWID;
import pro.gravit.launcher.HTTPRequest;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class JsonHWIDHandler extends HWIDHandler {
private static final Gson gson = new Gson();
private URL url;
private URL urlBan;
private URL urlUnBan;
private URL urlGet;
private String apiKey;
public static class banRequest {
public banRequest(OshiHWID hwid) {
this.hwid = hwid;
}
final OshiHWID hwid;
String apiKey;
public banRequest(OshiHWID hwid, String apiKey) {
this.hwid = hwid;
this.apiKey = apiKey;
}
}
public static class checkRequest {
public checkRequest(String username, OshiHWID hwid) {
this.username = username;
this.hwid = hwid;
}
final String username;
final OshiHWID hwid;
String apiKey;
public checkRequest(String username, OshiHWID hwid, String apiKey) {
this.username = username;
this.hwid = hwid;
this.apiKey = apiKey;
}
}
public static class Result {
String error;
}
public static class BannedResult {
boolean isBanned;
String error;
}
public static class HWIDRequest {
public HWIDRequest(String username) {
this.username = username;
}
final String username;
String apiKey;
public HWIDRequest(String username, String apiKey) {
this.username = username;
this.apiKey = apiKey;
}
}
@Override
public void ban(List<HWID> l_hwid) throws HWIDException {
for (HWID hwid : l_hwid) {
banRequest request = new banRequest((OshiHWID) hwid, apiKey);
try {
JsonElement result = HTTPRequest.jsonRequest(gson.toJsonTree(request), urlBan);
Result r = gson.fromJson(result, Result.class);
if (r.error != null) throw new HWIDException(r.error);
} catch (IOException e) {
LogHelper.error(e);
throw new HWIDException("HWID service error");
}
}
}
@Override
public void check0(HWID hwid, String username) throws HWIDException {
checkRequest request = new checkRequest(username, (OshiHWID) hwid, apiKey);
try {
JsonElement result = HTTPRequest.jsonRequest(gson.toJsonTree(request), url);
BannedResult r = gson.fromJson(result, BannedResult.class);
if (r.error != null) throw new HWIDException(r.error);
boolean isBanned = r.isBanned;
if (isBanned) throw new HWIDException("You will BANNED!");
} catch (IOException e) {
LogHelper.error(e);
throw new HWIDException("HWID service error");
}
}
@Override
public void close() {
// pass
}
@Override
public void init() {
}
@Override
public List<HWID> getHwid(String username) throws HWIDException {
ArrayList<HWID> hwids = new ArrayList<>();
HWIDRequest request = new HWIDRequest(username, apiKey);
try {
JsonElement result = HTTPRequest.jsonRequest(gson.toJsonTree(request), urlGet);
OshiHWID[] r = gson.fromJson(result, OshiHWID[].class);
hwids.addAll(Arrays.asList(r));
} catch (IOException e) {
LogHelper.error(e);
throw new HWIDException("HWID service error");
}
return hwids;
}
@Override
public void unban(List<HWID> l_hwid) throws HWIDException {
for (HWID hwid : l_hwid) {
banRequest request = new banRequest((OshiHWID) hwid, apiKey);
try {
JsonElement result = HTTPRequest.jsonRequest(gson.toJsonTree(request), urlUnBan);
Result r = gson.fromJson(result, Result.class);
if (r.error != null) throw new HWIDException(r.error);
} catch (IOException e) {
LogHelper.error(e);
throw new HWIDException("HWID service error");
}
}
}
}

View file

@ -0,0 +1,92 @@
package pro.gravit.launchserver.auth.hwid;
import pro.gravit.launcher.hwid.HWID;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class MemoryHWIDHandler extends HWIDHandler {
public static class Entry {
public final HWID hwid;
public String username;
public boolean isBanned = false;
public Entry(HWID hwid) {
this.hwid = hwid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(hwid, entry.hwid);
}
@Override
public int hashCode() {
return Objects.hash(hwid);
}
}
public final transient LinkedList<Entry> list = new LinkedList<>();
public final String banMessage = "You banned";
@Override
public void ban(List<HWID> hwid) {
for (Entry e : list) {
for (HWID banHWID : hwid) {
if (e.hwid.equals(banHWID)) {
e.isBanned = true;
break;
}
}
}
}
@Override
public void check0(HWID hwid, String username) throws HWIDException {
boolean isOne = false;
for (Entry e : list) {
if (e.hwid.equals(hwid)) {
isOne = true;
if (e.isBanned) throw new HWIDException(banMessage);
}
}
if (!isOne) {
list.add(new Entry(hwid));
}
}
@Override
public void close() {
}
@Override
public void init() {
}
@Override
public List<HWID> getHwid(String username) {
LinkedList<HWID> hwids = new LinkedList<>();
for (Entry e : list) {
if (e.username.equals(username)) hwids.add(e.hwid);
}
return hwids;
}
@Override
public void unban(List<HWID> hwid) {
for (Entry e : list) {
for (HWID banHWID : hwid) {
if (e.hwid.equals(banHWID)) {
e.isBanned = false;
break;
}
}
}
}
}

View file

@ -0,0 +1,268 @@
package pro.gravit.launchserver.auth.hwid;
import pro.gravit.launcher.hwid.HWID;
import pro.gravit.launcher.hwid.OshiHWID;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.LogHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class MysqlHWIDHandler extends HWIDHandler {
private MySQLSourceConfig mySQLHolder;
private String tableUsers;
private String tableHwids;
private String userFieldHwid;
private String userFieldLogin;
private String hwidFieldTotalMemory;
private String hwidFieldSerialNumber;
private String hwidFieldHWDiskSerial;
private String hwidFieldProcessorID;
private String hwidFieldBanned;
private String hwidFieldMAC;
private String queryHwids;
private String[] paramsHwids;
private String queryBan;
private String[] paramsBan;
private String banMessage;
private boolean compareMode = false;
//Using queryHWID "queryHwids": "SELECT * FROM `users_hwids` WHERE `totalMemory` = ? or `serialNumber` = ? or `HWDiskSerial` = ? or `processorID` = ? or `MACAddr` = ?"
private int compare = 50; //При наборе схожести в 50 очков
private boolean oneCompareMode = false;
/*
//Добавить поля hwid в базу с пользователями
//Создание таблицы для хранения HWID
CREATE TABLE `fc_user_hwids` (
`id` int(16) NOT NULL,
`totalMemory` varchar(32) NOT NULL,
`serialNumber` varchar(64) NOT NULL,
`HWDiskSerial` varchar(64) NOT NULL,
`processorID` varchar(64) NOT NULL,
`MACAddr` varchar(64) NOT NULL,
`isBanned` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `fc_user_hwids` ADD UNIQUE KEY `id` (`id`);
ALTER TABLE `fc_user_hwids` MODIFY `id` int(16) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
*/
@Override
public void check0(HWID hwid, String username) throws HWIDException {
if (hwid instanceof OshiHWID) {
OshiHWID oshiHWID = (OshiHWID) hwid;
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(String.format("SELECT %s, %s FROM `%s` WHERE `%s` = ? LIMIT 1",
userFieldHwid, userFieldLogin, tableUsers, userFieldLogin));
s.setString(1, username);
// Execute SQL query
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
int hwid_id = set.getInt(userFieldHwid);
if (hwid_id == 0) {
onUpdateInfo(oshiHWID, username, c);
} else {
onCheckInfo(oshiHWID, username, c);
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public void onUpdateInfo(OshiHWID hwid, String username, Connection c) throws HWIDException {
try (PreparedStatement a = c.prepareStatement(queryHwids)) {
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID, "MAC", hwid.macAddr};
for (int i = 0; i < paramsHwids.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsHwids[i], replaceParams));
}
ResultSet set = a.executeQuery();
PreparedStatement ps;
if (set.next()) {
int id = set.getInt("id");
boolean isBanned = set.getBoolean(hwidFieldBanned);
ps = c.prepareStatement(String.format("UPDATE `%s` SET `%s` = ? WHERE `%s` = ?",
tableUsers, userFieldHwid, userFieldLogin));
ps.setInt(1, id);
ps.setString(2, username);
ps.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ps.executeUpdate();
if (isBanned) {
throw new HWIDException(banMessage);
}
} else {
ps = c.prepareStatement(String.format("INSERT INTO `%s` (`%s`, `%s`, `%s`, `%s`, `%s`) VALUES (?, ?, ?, ?, ?);",
tableHwids, hwidFieldTotalMemory, hwidFieldSerialNumber, hwidFieldHWDiskSerial, hwidFieldProcessorID, hwidFieldMAC));
ps.setString(1, String.valueOf(hwid.totalMemory));
ps.setString(2, hwid.serialNumber);
ps.setString(3, hwid.HWDiskSerial);
ps.setString(4, hwid.processorID);
ps.setString(5, hwid.macAddr);
ps.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ps.executeUpdate();
ps = c.prepareStatement(String.format("UPDATE `%s` SET `%s` = LAST_INSERT_ID() WHERE `%s` = ?;",
tableUsers, userFieldHwid, userFieldLogin));
ps.setString(1, username);
ps.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
ps.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void onCheckInfo(OshiHWID hwid, String username, Connection c) throws HWIDException {
try (PreparedStatement a = c.prepareStatement(queryHwids)) {
String[] replaceParams = {"totalMemory", String.valueOf(hwid.totalMemory), "serialNumber", hwid.serialNumber, "HWDiskSerial", hwid.HWDiskSerial, "processorID", hwid.processorID, "MAC", hwid.macAddr};
for (int i = 0; i < paramsHwids.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsHwids[i], replaceParams));
}
ResultSet set = a.executeQuery();
boolean isOne = false;
while (set.next()) {
if (!oneCompareMode) isOne = true;
if (compareMode) {
OshiHWID db_hwid = new OshiHWID();
db_hwid.serialNumber = set.getString(hwidFieldSerialNumber);
db_hwid.processorID = set.getString(hwidFieldProcessorID);
db_hwid.HWDiskSerial = set.getString(hwidFieldHWDiskSerial);
db_hwid.totalMemory = Long.parseLong(set.getString(hwidFieldTotalMemory));
db_hwid.macAddr = set.getString(hwidFieldMAC);
if (LogHelper.isDevEnabled()) {
LogHelper.dev("Compare HWID: %s vs %s", hwid.toString(), db_hwid.toString());
}
int compare_point = hwid.compare(db_hwid);
if (compare_point < compare) continue;
else {
if (LogHelper.isDevEnabled()) {
LogHelper.debug("User %s hwid check: found compare %d in %d", username, compare_point, set.getInt("id"));
}
}
}
if (oneCompareMode) isOne = true;
boolean isBanned = set.getBoolean(hwidFieldBanned);
if (isBanned) {
throw new HWIDException(banMessage);
}
}
if (isOne) {
onUpdateInfo(hwid, username, c);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void setIsBanned(HWID hwid, boolean isBanned) {
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("%s Request HWID: %s", isBanned ? "Ban" : "UnBan", hwid.toString());
}
if (hwid instanceof OshiHWID) {
OshiHWID oshiHWID = (OshiHWID) hwid;
try (Connection c = mySQLHolder.getConnection()) {
try (PreparedStatement a = c.prepareStatement(queryBan)) {
String[] replaceParamsUpd = {"totalMemory", String.valueOf(oshiHWID.totalMemory), "serialNumber", oshiHWID.serialNumber, "HWDiskSerial", oshiHWID.HWDiskSerial, "processorID", oshiHWID.processorID, "MAC", oshiHWID.macAddr, "isBanned", isBanned ? "1" : "0"};
for (int i = 0; i < paramsBan.length; i++) {
a.setString(i + 1, CommonHelper.replace(paramsBan[i], replaceParamsUpd));
}
a.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
a.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void ban(List<HWID> list) {
for (HWID hwid : list) {
setIsBanned(hwid, true);
}
}
@Override
public void unban(List<HWID> list) {
for (HWID hwid : list) {
setIsBanned(hwid, false);
}
}
@Override
public List<HWID> getHwid(String username) {
ArrayList<HWID> list = new ArrayList<>();
try (Connection c = mySQLHolder.getConnection()) {
if (LogHelper.isDebugEnabled()) {
LogHelper.debug("Try find HWID from username %s", username);
}
PreparedStatement s = c.prepareStatement(String.format("SELECT %s, %s FROM `%s` WHERE `%s` = ? LIMIT 1", userFieldHwid, userFieldLogin, tableUsers, userFieldLogin));
s.setString(1, username);
// Execute SQL query
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
if (set.next()) {
int hwid_id = set.getInt(userFieldHwid);
if (hwid_id != 0) {
s = c.prepareStatement(String.format("SELECT * FROM `%s` WHERE `id` = ? LIMIT 1", tableHwids));
s.setInt(1, hwid_id);
ResultSet rs = s.executeQuery();
if (rs.next()) {
OshiHWID oshiHWID = new OshiHWID();
oshiHWID.totalMemory = Long.parseLong(rs.getString(hwidFieldTotalMemory));
oshiHWID.serialNumber = rs.getString(hwidFieldSerialNumber);
oshiHWID.HWDiskSerial = rs.getString(hwidFieldHWDiskSerial);
oshiHWID.processorID = rs.getString(hwidFieldProcessorID);
oshiHWID.macAddr = rs.getString(hwidFieldMAC);
list.add(oshiHWID);
}
}
} else {
LogHelper.error(new HWIDException("HWID not found"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
@Override
public void close() {
mySQLHolder.close();
}
@Override
public void init() {
}
}

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,9 +1,23 @@
package pro.gravit.launchserver.auth.protect; package pro.gravit.launchserver.auth.protect;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
public class NoProtectHandler extends ProtectHandler { public class NoProtectHandler extends ProtectHandler {
@Override
public String generateSecureToken(AuthResponse.AuthContext context) {
return SecurityHelper.randomStringToken();
}
@Override
public String generateClientSecureToken() {
return SecurityHelper.randomStringToken();
}
@Override
public boolean verifyClientSecureToken(String token, String secureKey) {
return true;
}
@Override @Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) { public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@ -11,7 +25,7 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
} }
@Override @Override
public boolean allowJoinServer(Client client) { public void checkLaunchServerLicense() {
return true; // None
} }
} }

View file

@ -1,7 +1,5 @@
package pro.gravit.launchserver.auth.protect; 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.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
@ -14,22 +12,18 @@ public static void registerHandlers() {
if (!registredHandl) { if (!registredHandl) {
providers.register("none", NoProtectHandler.class); providers.register("none", NoProtectHandler.class);
providers.register("std", StdProtectHandler.class); providers.register("std", StdProtectHandler.class);
providers.register("advanced", AdvancedProtectHandler.class);
registredHandl = true; registredHandl = true;
} }
} }
public abstract String generateSecureToken(AuthResponse.AuthContext context); //Генерация токена для передачи его в LauncherGuardInterface
public abstract String generateClientSecureToken();
public abstract boolean verifyClientSecureToken(String token, String secureKey);
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context); public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
public boolean allowJoinServer(Client client) {
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT;
}
public void init(LaunchServer server) { public abstract void checkLaunchServerLicense(); //Выдает SecurityException при ошибке проверки лицензии
}
public void close() {
}
//public abstract //public abstract
} }

View file

@ -1,59 +1,33 @@
package pro.gravit.launchserver.auth.protect; 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 pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import java.util.*; public class StdProtectHandler extends ProtectHandler {
public final boolean checkSecure = true;
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler { @Override
private transient final Logger logger = LogManager.getLogger(); public String generateSecureToken(AuthResponse.AuthContext context) {
public Map<String, List<String>> profileWhitelist = new HashMap<>(); return SecurityHelper.randomStringToken();
public List<String> allowUpdates = new ArrayList<>(); }
@Override
public String generateClientSecureToken() {
return SecurityHelper.randomStringToken();
}
@Override
public boolean verifyClientSecureToken(String token, String secureKey) {
return true;
}
@Override @Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) { public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign; return (context.authType == AuthResponse.ConnectTypes.CLIENT) && (!checkSecure || context.client.isSecure);
} }
@Override @Override
public void init(LaunchServer server) { public void checkLaunchServerLicense() {
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

@ -0,0 +1,18 @@
package pro.gravit.launchserver.auth.provider;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.utils.helper.SecurityHelper;
public final class AcceptAuthProvider extends AuthProvider {
@Override
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) {
return new AuthProviderResult(login, SecurityHelper.randomStringToken(), ClientPermissions.DEFAULT); // Same as login
}
@Override
public void close() {
// Do nothing
}
}

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