mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-04-24 00:43:03 +03:00
Compare commits
No commits in common. "master" and "v5.1.2" have entirely different histories.
692 changed files with 16720 additions and 24791 deletions
86
.gitattributes
vendored
86
.gitattributes
vendored
|
@ -1,78 +1,26 @@
|
||||||
* text=auto eol=lf
|
* text eol=lf
|
||||||
*.[cC][mM][dD] text eol=crlf
|
*.bat text eol=crlf
|
||||||
*.[bB][aA][tT] text eol=crlf
|
*.sh text eol=lf
|
||||||
*.[pP][sS]1 text eol=crlf
|
|
||||||
*.[sS][hH] text eol=lf
|
|
||||||
|
|
||||||
*.patch text eol=lf
|
*.patch text eol=lf
|
||||||
|
*.java text eol=lf
|
||||||
|
*.scala text eol=lf
|
||||||
|
*.groovy text eol=lf
|
||||||
|
|
||||||
|
*.gradle text eol=crlf
|
||||||
|
gradle.properties text eol=crlf
|
||||||
|
/gradle/wrapper/gradle-wrapper.properties text eol=crlf
|
||||||
|
*.cfg text eol=lf
|
||||||
|
|
||||||
*.png binary
|
*.png binary
|
||||||
|
*.jar binary
|
||||||
|
*.war binary
|
||||||
*.lzma binary
|
*.lzma binary
|
||||||
*.zip binary
|
*.zip binary
|
||||||
*.gzip binary
|
*.gzip binary
|
||||||
|
*.dll binary
|
||||||
|
*.so binary
|
||||||
*.exe binary
|
*.exe binary
|
||||||
*.ico binary
|
|
||||||
*.eot binary
|
|
||||||
*.ttf binary
|
|
||||||
*.woff binary
|
|
||||||
*.woff2 binary
|
|
||||||
*.a binary
|
|
||||||
*.lib binary
|
|
||||||
*.icns binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
*.gif binary
|
|
||||||
*.mov binary
|
|
||||||
*.mp4 binary
|
|
||||||
*.mp3 binary
|
|
||||||
*.flv binary
|
|
||||||
*.fla binary
|
|
||||||
*.swf binary
|
|
||||||
*.gz binary
|
|
||||||
*.tar binary
|
|
||||||
*.tar.gz binary
|
|
||||||
*.7z binary
|
|
||||||
*.pyc binary
|
|
||||||
*.gpg binary
|
|
||||||
*.bin binary
|
|
||||||
|
|
||||||
*.gitattributes text
|
*.gitattributes text eol=crlf
|
||||||
.gitignore text
|
*.gitignore text eol=crlf
|
||||||
|
|
||||||
# Java sources
|
|
||||||
*.java text diff=java
|
|
||||||
*.kt text diff=kotlin
|
|
||||||
*.groovy text diff=java
|
|
||||||
*.scala text diff=java
|
|
||||||
*.gradle text diff=java
|
|
||||||
*.gradle.kts text diff=kotlin
|
|
||||||
|
|
||||||
# These files are text and should be normalized (Convert crlf => lf)
|
|
||||||
*.css text diff=css
|
|
||||||
*.scss text diff=css
|
|
||||||
*.sass text
|
|
||||||
*.df text
|
|
||||||
*.htm text diff=html
|
|
||||||
*.html text diff=html
|
|
||||||
*.js text
|
|
||||||
*.jsp text
|
|
||||||
*.jspf text
|
|
||||||
*.jspx text
|
|
||||||
*.properties text
|
|
||||||
*.tld text
|
|
||||||
*.tag text
|
|
||||||
*.tagx text
|
|
||||||
*.xml text
|
|
||||||
|
|
||||||
# These files are binary and should be left untouched
|
|
||||||
# (binary is a macro for -text -diff)
|
|
||||||
*.class binary
|
|
||||||
*.dll binary
|
|
||||||
*.ear binary
|
|
||||||
*.jar binary
|
|
||||||
*.so binary
|
|
||||||
*.war binary
|
|
||||||
*.jks binary
|
|
||||||
|
|
||||||
mvnw text eol=lf
|
|
||||||
gradlew text eol=lf
|
|
||||||
|
|
79
.github/workflows/push.yml
vendored
79
.github/workflows/push.yml
vendored
|
@ -1,79 +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
|
|
||||||
|
|
||||||
- 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
1
.gitignore
vendored
|
@ -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/
|
||||||
|
|
90
.gitlab-ci.yml
Normal file
90
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
image: gradle:jdk11
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
variables:
|
||||||
|
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- apt-get -y update
|
||||||
|
- 'which zip || ( apt-get -y install zip )'
|
||||||
|
- 'which git || ( apt-get -y install git )'
|
||||||
|
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||||
|
- chmod +x gradlew
|
||||||
|
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||||
|
- eval $(ssh-agent -s)
|
||||||
|
- echo "$SSH_PRIVATE_KEY" | base64 -d | ssh-add - > /dev/null
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- chmod 700 ~/.ssh
|
||||||
|
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
|
||||||
|
- git submodule sync
|
||||||
|
- mv modules modules_cache || true
|
||||||
|
- git submodule update --init --recursive --force
|
||||||
|
- cp -a modules_cache/* modules/ || true
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- gradle assemble
|
||||||
|
after_script:
|
||||||
|
- 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 ../../../ServerWrapper/build/libs
|
||||||
|
- cp ServerWrapper.jar ../../../artifacts/ServerWrapper.jar
|
||||||
|
- cd ../../../LauncherAuthlib/build/libs
|
||||||
|
- cp LauncherAuthlib.jar ../../../artifacts/LauncherAuthlib.jar
|
||||||
|
- cd ../../../
|
||||||
|
- cp modules/*_module/build/libs/*.jar artifacts/modules
|
||||||
|
- cp modules/*_swmodule/build/libs/*.jar artifacts/modules
|
||||||
|
- cp modules/*_lmodule/build/libs/*.jar artifacts/modules
|
||||||
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_NAME"
|
||||||
|
paths:
|
||||||
|
- .gradle
|
||||||
|
- LaunchServer/build
|
||||||
|
- Launcher/build
|
||||||
|
- LauncherCore/build
|
||||||
|
- LauncherAPI/build
|
||||||
|
- LauncherAuthlib/build
|
||||||
|
- modules/*_*module/build
|
||||||
|
artifacts:
|
||||||
|
expire_in: 6 week
|
||||||
|
paths:
|
||||||
|
- artifacts
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- gradle check
|
||||||
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_NAME"
|
||||||
|
policy: pull
|
||||||
|
paths:
|
||||||
|
- .gradle
|
||||||
|
- LaunchServer/build
|
||||||
|
- Launcher/build
|
||||||
|
- LauncherCore/build
|
||||||
|
- LauncherAPI/build
|
||||||
|
- LauncherAuthlib/build
|
||||||
|
- modules/*_*module/build
|
||||||
|
|
||||||
|
deploy-demo:
|
||||||
|
stage: deploy
|
||||||
|
only: [dev]
|
||||||
|
script:
|
||||||
|
- gradle build
|
||||||
|
- eval $(ssh $SSH_USER@$SSH_HOST 'cd $SSH_DIR && cat deploy.sh')
|
||||||
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_NAME"
|
||||||
|
policy: pull
|
||||||
|
paths:
|
||||||
|
- .gradle
|
||||||
|
- LaunchServer/build
|
||||||
|
- Launcher/build
|
||||||
|
- LauncherCore/build
|
||||||
|
- LauncherAPI/build
|
||||||
|
- LauncherAuthlib/build
|
||||||
|
- modules/*_*module/build
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -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
22
.travis.yml
Normal 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
|
|
@ -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* |
|
||||||
|
|
||||||
-----
|
-----
|
||||||

|

|
||||||
-----
|
-----
|
||||||
|
|
||||||
## Коммиты ##
|
## Коммиты ##
|
||||||
|
**Основные правила:**
|
||||||
**Основные правила:**
|
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 |
|
||||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -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
|
|
|
@ -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,29 +8,22 @@
|
||||||
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
|
||||||
|
launch4j
|
||||||
bundleOnly.extendsFrom bundle
|
bundleOnly.extendsFrom bundle
|
||||||
api.extendsFrom bundle, pack
|
api.extendsFrom bundle, hikari, pack, launch4j
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
@ -40,7 +33,9 @@
|
||||||
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
|
from(parent.childProjects.Launcher.tasks.genRuntimeJS)
|
||||||
manifest.attributes("Main-Class": mainClassName,
|
manifest.attributes("Main-Class": mainClassName,
|
||||||
"Premain-Class": mainAgentName,
|
"Premain-Class": mainAgentName,
|
||||||
"Multi-Release": "true",
|
"Can-Redefine-Classes": "true",
|
||||||
|
"Can-Retransform-Classes": "true",
|
||||||
|
"Can-Set-Native-Method-Prefix": "true"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,21 +46,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -73,71 +70,97 @@
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
pack project(':LauncherAPI')
|
pack project(':LauncherAPI')
|
||||||
bundle group: 'me.tongfei', name: 'progressbar', version: '0.10.1'
|
bundle group: 'org.fusesource.jansi', name:'jansi', version: rootProject['verJansi']
|
||||||
bundle group: 'org.fusesource.jansi', name: 'jansi', version: rootProject['verJansi']
|
|
||||||
bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline', version: rootProject['verJline']
|
||||||
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline-reader', version: rootProject['verJline']
|
||||||
bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
|
bundle group: 'org.jline', name: 'jline-terminal', version: rootProject['verJline']
|
||||||
bundle group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: rootProject['verBcpkix']
|
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: rootProject['verBcpkix']
|
||||||
bundle group: 'org.bouncycastle', name: 'bcpkix-jdk18on', version: rootProject['verBcpkix']
|
|
||||||
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
|
bundle group: 'org.ow2.asm', name: 'asm-commons', version: rootProject['verAsm']
|
||||||
bundle group: 'io.netty', name: 'netty-codec-http', version: rootProject['verNetty']
|
bundle group: 'io.netty', name: 'netty-all', version: rootProject['verNetty']
|
||||||
bundle group: 'io.netty', name: 'netty-transport-classes-epoll', version: rootProject['verNetty']
|
bundle group: 'org.slf4j', name: 'slf4j-simple', version: rootProject['verSlf4j']
|
||||||
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-x86_64'
|
|
||||||
bundle group: 'io.netty', name: 'netty-transport-native-epoll', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
|
|
||||||
bundle group: 'io.netty', name: 'netty-transport-classes-io_uring', version: rootProject['verNetty']
|
|
||||||
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-x86_64'
|
|
||||||
bundle group: 'io.netty', name: 'netty-transport-native-io_uring', version: rootProject['verNetty'], classifier: 'linux-aarch_64'
|
|
||||||
// Netty
|
|
||||||
bundle 'org.jboss.marshalling:jboss-marshalling:1.4.11.Final'
|
|
||||||
bundle 'com.google.protobuf.nano:protobuf-javanano:3.1.0'
|
|
||||||
//
|
|
||||||
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
|
bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j']
|
||||||
bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn']
|
bundle group: 'org.hibernate', name: 'hibernate-core', version: rootProject['verHibernate']
|
||||||
bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn']
|
bundle group: 'mysql', name: 'mysql-connector-java', version: rootProject['verMySQLConn']
|
||||||
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
|
bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn']
|
||||||
bundle group: 'com.h2database', name: 'h2', version: rootProject['verH2Conn']
|
bundle group: 'net.sf.proguard', name: 'proguard-base', version: rootProject['verProguard']
|
||||||
bundle 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']
|
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: rootProject['verJunit']
|
||||||
|
|
||||||
bundle 'io.micrometer:micrometer-core:1.14.4'
|
hikari 'io.micrometer:micrometer-core:1.0.6'
|
||||||
bundle('com.zaxxer:HikariCP:6.2.1') {
|
hikari('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:' + rootProject['verLaunch4j']) {
|
||||||
|
exclude group: 'org.apache.ant'
|
||||||
|
exclude group: 'net.java.abeille'
|
||||||
|
exclude group: 'foxtrot'
|
||||||
|
exclude group: 'com.jgoodies'
|
||||||
|
exclude group: 'org.slf4j'
|
||||||
|
}
|
||||||
|
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + ':workdir-win32') { transitive = false }
|
||||||
|
launch4j('net.sf.launch4j:launch4j:' + rootProject['verLaunch4j'] + 'workdir-linux') { transitive = false }
|
||||||
|
|
||||||
|
compileOnlyA group: 'com.google.guava', name: 'guava', version: rootProject['verGuavaC']
|
||||||
|
// Do not update (laggy deps).
|
||||||
|
compileOnlyA 'log4j:log4j:1.2.17'
|
||||||
|
compileOnlyA 'org.apache.logging.log4j:log4j-core:2.11.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('dumpLibs', Copy) {
|
task hikari(type: Copy) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
|
into "$buildDir/libs/libraries/hikaricp"
|
||||||
|
from configurations.hikari
|
||||||
|
}
|
||||||
|
|
||||||
|
task launch4j(type: Copy) {
|
||||||
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
|
into "$buildDir/libs/libraries/launch4j"
|
||||||
|
from(configurations.launch4j.collect { it.isDirectory() ? it : ((it.getName().startsWith("launch4j") && it.getName().contains("workdir")) ? zipTree(it) : it) })
|
||||||
|
includeEmptyDirs false
|
||||||
|
eachFile { FileCopyDetails fcp ->
|
||||||
|
if (fcp.relativePath.pathString.startsWith("launch4j-") &&
|
||||||
|
fcp.relativePath.pathString.contains("workdir")) {
|
||||||
|
def segments = fcp.relativePath.segments
|
||||||
|
def pathSegments = segments[1..-1] as String[]
|
||||||
|
fcp.relativePath = new RelativePath(!fcp.file.isDirectory(), pathSegments)
|
||||||
|
} else if (fcp.relativePath.pathString.contains("META-INF")) fcp.exclude()
|
||||||
|
fcp.mode = 0755
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task dumpLibs(type: Copy) {
|
||||||
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
|
dependsOn tasks.hikari, tasks.launch4j
|
||||||
into "$buildDir/libs/libraries"
|
into "$buildDir/libs/libraries"
|
||||||
from configurations.bundleOnly
|
from configurations.bundleOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('bundle', Zip) {
|
task dumpCompileOnlyLibs(type: Copy) {
|
||||||
duplicatesStrategy = 'EXCLUDE'
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.jar
|
into "$buildDir/libs/launcher-libraries-compile"
|
||||||
|
from configurations.compileOnlyA
|
||||||
|
}
|
||||||
|
|
||||||
|
task bundle(type: Zip) {
|
||||||
|
duplicatesStrategy = 'EXCLUDE'
|
||||||
|
dependsOn parent.childProjects.Launcher.tasks.build, tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.jar
|
||||||
archiveFileName = 'LaunchServer.zip'
|
archiveFileName = 'LaunchServer.zip'
|
||||||
destinationDirectory = file("$buildDir")
|
destinationDirectory = file("$buildDir")
|
||||||
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
|
from(tasks.dumpLibs.destinationDir) { into 'libraries' }
|
||||||
|
from(tasks.dumpCompileOnlyLibs.destinationDir) { into 'launcher-libraries-compile' }
|
||||||
from(tasks.jar)
|
from(tasks.jar)
|
||||||
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
|
from(parent.childProjects.Launcher.tasks.dumpLibs) { into 'launcher-libraries' }
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('dumpClientLibs', Copy) {
|
task dumpClientLibs(type: Copy) {
|
||||||
dependsOn parent.childProjects.Launcher.tasks.build
|
dependsOn parent.childProjects.Launcher.tasks.build
|
||||||
into "$buildDir/libs/launcher-libraries"
|
into "$buildDir/libs/launcher-libraries"
|
||||||
from parent.childProjects.Launcher.tasks.dumpLibs
|
from parent.childProjects.Launcher.tasks.dumpLibs
|
||||||
}
|
}
|
||||||
|
|
||||||
assemble.dependsOn tasks.dumpLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
|
assemble.dependsOn tasks.dumpLibs, tasks.dumpCompileOnlyLibs, tasks.dumpClientLibs, tasks.bundle, tasks.cleanjar
|
||||||
|
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -152,7 +175,7 @@ pack project(':LauncherAPI')
|
||||||
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'
|
||||||
|
@ -161,9 +184,9 @@ pack project(':LauncherAPI')
|
||||||
}
|
}
|
||||||
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'
|
||||||
|
@ -175,7 +198,7 @@ pack project(':LauncherAPI')
|
||||||
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/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -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,47 +73,37 @@ 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() {
|
||||||
|
@Override
|
||||||
|
public LaunchServerConfig readConfig() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeConfig(LaunchServerConfig config) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (keyAgreementManager == null) {
|
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, publicKey, privateKey, commandHandler, certificateManager);
|
||||||
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) {
|
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
|
||||||
this.certificateManager = certificateManager;
|
this.certificateManager = certificateManager;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyAgreementManager(KeyAgreementManager keyAgreementManager) {
|
|
||||||
this.keyAgreementManager = keyAgreementManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NullLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
|
||||||
@Override
|
|
||||||
public LaunchServerConfig readConfig() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LaunchServerRuntimeConfig readRuntimeConfig() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeConfig(LaunchServerConfig config) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
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.modules.events.PreConfigPhase;
|
||||||
import pro.gravit.launcher.base.modules.events.PreConfigPhase;
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
import pro.gravit.launcher.base.profiles.optional.actions.OptionalAction;
|
import pro.gravit.launchserver.auth.handler.AuthHandler;
|
||||||
import pro.gravit.launcher.base.profiles.optional.triggers.OptionalTrigger;
|
|
||||||
import pro.gravit.launcher.base.request.auth.AuthRequest;
|
|
||||||
import pro.gravit.launcher.base.request.auth.GetAvailabilityAuthRequest;
|
|
||||||
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
|
|
||||||
import pro.gravit.launchserver.auth.mix.MixProvider;
|
|
||||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
|
||||||
import pro.gravit.launchserver.auth.profiles.ProfileProvider;
|
|
||||||
import pro.gravit.launchserver.auth.protect.ProtectHandler;
|
import pro.gravit.launchserver.auth.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 +22,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");
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
ECPublicKey publicKey;
|
||||||
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
|
ECPrivateKey privateKey;
|
||||||
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
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,61 +96,104 @@ 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() {
|
||||||
|
@Override
|
||||||
|
public LaunchServerConfig readConfig() throws IOException {
|
||||||
|
LaunchServerConfig config1;
|
||||||
|
try (BufferedReader reader = IOHelper.newReader(configFile)) {
|
||||||
|
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
|
||||||
|
}
|
||||||
|
return config1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
||||||
|
LaunchServerRuntimeConfig config1;
|
||||||
|
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
||||||
|
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
||||||
|
}
|
||||||
|
return config1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeConfig(LaunchServerConfig config) throws IOException {
|
||||||
|
try (Writer writer = IOHelper.newWriter(configFile)) {
|
||||||
|
if (Launcher.gsonManager.configGson != null) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(config, writer);
|
||||||
|
} else {
|
||||||
|
LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
|
||||||
|
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
|
||||||
|
if (Launcher.gsonManager.configGson != null) {
|
||||||
|
Launcher.gsonManager.configGson.toJson(config, writer);
|
||||||
|
} else {
|
||||||
|
LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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()
|
LaunchServer server = new LaunchServerBuilder()
|
||||||
.setDirectories(directories)
|
.setDirectories(directories)
|
||||||
.setEnv(env)
|
.setEnv(env)
|
||||||
.setCommandHandler(localCommandHandler)
|
.setCommandHandler(localCommandHandler)
|
||||||
|
.setPrivateKey(privateKey)
|
||||||
|
.setPublicKey(publicKey)
|
||||||
.setRuntimeConfig(runtimeConfig)
|
.setRuntimeConfig(runtimeConfig)
|
||||||
.setConfig(config)
|
.setConfig(config)
|
||||||
.setModulesManager(modulesManager)
|
.setModulesManager(modulesManager)
|
||||||
.setLaunchServerConfigManager(launchServerConfigManager)
|
.setLaunchServerConfigManager(launchServerConfigManager)
|
||||||
.setCertificateManager(certificateManager)
|
.setCertificateManager(certificateManager)
|
||||||
.build();
|
.build();
|
||||||
List<String> allArgs = List.of(args);
|
server.run();
|
||||||
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) {
|
public static void initGson(LaunchServerModulesManager modulesManager) {
|
||||||
|
@ -168,39 +202,15 @@ public static void initGson(LaunchServerModulesManager modulesManager) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerAll() {
|
public static void registerAll() {
|
||||||
AuthCoreProvider.registerProviders();
|
|
||||||
PasswordVerifier.registerProviders();
|
AuthHandler.registerHandlers();
|
||||||
|
AuthProvider.registerProviders();
|
||||||
TextureProvider.registerProviders();
|
TextureProvider.registerProviders();
|
||||||
Component.registerComponents();
|
Component.registerComponents();
|
||||||
ProtectHandler.registerHandlers();
|
ProtectHandler.registerHandlers();
|
||||||
WebSocketService.registerResponses();
|
WebSocketService.registerResponses();
|
||||||
|
DaoProvider.registerProviders();
|
||||||
AuthRequest.registerProviders();
|
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 {
|
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
|
||||||
|
@ -208,7 +218,7 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Create new config
|
// Create new config
|
||||||
logger.info("Creating LaunchServer config");
|
LogHelper.info("Creating LaunchServer config");
|
||||||
|
|
||||||
|
|
||||||
LaunchServerConfig newConfig = LaunchServerConfig.getDefault(env);
|
LaunchServerConfig newConfig = LaunchServerConfig.getDefault(env);
|
||||||
|
@ -218,113 +228,29 @@ public static void generateConfigIfNotExists(Path configFile, CommandHandler com
|
||||||
address = "localhost";
|
address = "localhost";
|
||||||
newConfig.setProjectName("test");
|
newConfig.setProjectName("test");
|
||||||
} else {
|
} else {
|
||||||
address = System.getenv("ADDRESS");
|
System.out.println("LaunchServer address(default: localhost): ");
|
||||||
if (address == null) {
|
address = commandHandler.readLine();
|
||||||
address = System.getProperty("launchserver.address", null);
|
System.out.println("LaunchServer projectName: ");
|
||||||
}
|
newConfig.setProjectName(commandHandler.readLine());
|
||||||
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()) {
|
if (address == null || address.isEmpty()) {
|
||||||
logger.error("Address null. Using localhost:9274");
|
LogHelper.error("Address null. Using localhost");
|
||||||
address = "localhost:9274";
|
address = "localhost";
|
||||||
}
|
}
|
||||||
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
|
if (newConfig.projectName == null || newConfig.projectName.isEmpty()) {
|
||||||
logger.error("ProjectName null. Using MineCraft");
|
LogHelper.error("ProjectName null. Using MineCraft");
|
||||||
newConfig.projectName = "MineCraft";
|
newConfig.projectName = "MineCraft";
|
||||||
}
|
}
|
||||||
int port = 9274;
|
|
||||||
if(address.contains(":")) {
|
newConfig.netty.address = "ws://" + address + ":9274/api";
|
||||||
String portString = address.substring(address.indexOf(':')+1);
|
newConfig.netty.downloadURL = "http://" + address + ":9274/%dirname%/";
|
||||||
try {
|
newConfig.netty.launcherURL = "http://" + address + ":9274/Launcher.jar";
|
||||||
port = Integer.parseInt(portString);
|
newConfig.netty.launcherEXEURL = "http://" + address + ":9274/Launcher.exe";
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
logger.warn("Unknown port {}, using 9274", portString);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.info("Address {} doesn't contains port (you want to use nginx?)", address);
|
|
||||||
}
|
|
||||||
newConfig.netty.address = "ws://" + address + "/api";
|
|
||||||
newConfig.netty.downloadURL = "http://" + address + "/%dirname%/";
|
|
||||||
newConfig.netty.launcherURL = "http://" + address + "/Launcher.jar";
|
|
||||||
newConfig.netty.launcherEXEURL = "http://" + address + "/Launcher.exe";
|
|
||||||
newConfig.netty.binds[0].port = port;
|
|
||||||
|
|
||||||
// Write LaunchServer config
|
// Write LaunchServer config
|
||||||
logger.info("Writing LaunchServer config file");
|
LogHelper.info("Writing LaunchServer config file");
|
||||||
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
|
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
|
||||||
Launcher.gsonManager.configGson.toJson(newConfig, writer);
|
Launcher.gsonManager.configGson.toJson(newConfig, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BasicLaunchServerConfigManager implements LaunchServer.LaunchServerConfigManager {
|
|
||||||
private final Path configFile;
|
|
||||||
private final Path runtimeConfigFile;
|
|
||||||
|
|
||||||
public BasicLaunchServerConfigManager(Path configFile, Path runtimeConfigFile) {
|
|
||||||
this.configFile = configFile;
|
|
||||||
this.runtimeConfigFile = runtimeConfigFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LaunchServerConfig readConfig() throws IOException {
|
|
||||||
LaunchServerConfig config1;
|
|
||||||
try (BufferedReader reader = IOHelper.newReader(configFile)) {
|
|
||||||
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerConfig.class);
|
|
||||||
}
|
|
||||||
return config1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
|
||||||
LaunchServerRuntimeConfig config1;
|
|
||||||
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
|
|
||||||
config1 = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
|
|
||||||
}
|
|
||||||
return config1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeConfig(LaunchServerConfig config) throws IOException {
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
try (Writer writer = IOHelper.newWriter(output)) {
|
|
||||||
if (Launcher.gsonManager.configGson != null) {
|
|
||||||
Launcher.gsonManager.configGson.toJson(config, writer);
|
|
||||||
} else {
|
|
||||||
logger.error("Error writing LaunchServer config file. Gson is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte[] bytes = output.toByteArray();
|
|
||||||
if(bytes.length > 0) {
|
|
||||||
IOHelper.write(configFile, bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
try (Writer writer = IOHelper.newWriter(output)) {
|
|
||||||
if (Launcher.gsonManager.configGson != null) {
|
|
||||||
Launcher.gsonManager.configGson.toJson(config, writer);
|
|
||||||
} else {
|
|
||||||
logger.error("Error writing LaunchServer runtime config file. Gson is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte[] bytes = output.toByteArray();
|
|
||||||
if(bytes.length > 0) {
|
|
||||||
IOHelper.write(runtimeConfigFile, bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +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", "jline", "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");
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,20 +18,35 @@
|
||||||
* чего угодно. Работает через поиск class-файлов в classpath.
|
* чего угодно. Работает через поиск class-файлов в classpath.
|
||||||
*/
|
*/
|
||||||
public class ClassMetadataReader implements Closeable {
|
public class ClassMetadataReader implements Closeable {
|
||||||
|
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;
|
||||||
|
|
||||||
public ClassMetadataReader(List<JarFile> cp) {
|
public ClassMetadataReader(List<JarFile> cp) {
|
||||||
this.cp = cp;
|
this.cp = cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClassMetadataReader() {
|
|
||||||
this.cp = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -48,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) {
|
||||||
|
@ -95,19 +111,4 @@ public void close() {
|
||||||
cp.clear();
|
cp.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,254 +1,245 @@
|
||||||
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 static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class,
|
private final Map<String, Object> values;
|
||||||
java.lang.Float.class, java.lang.Double.class, java.lang.String.class);
|
|
||||||
private static final String INJECTED_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
|
|
||||||
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.class);
|
|
||||||
private static final List<String> primitiveLDCDescriptors = Arrays.asList(Type.INT_TYPE.getDescriptor(), Type.DOUBLE_TYPE.getDescriptor(),
|
|
||||||
Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
|
|
||||||
private static final Map<Class<?>, Serializer<?>> serializers;
|
|
||||||
|
|
||||||
static {
|
public InjectClassAcceptor(Map<String, Object> values) {
|
||||||
serializers = new HashMap<>();
|
this.values = values;
|
||||||
serializers.put(List.class, new ListSerializer());
|
}
|
||||||
serializers.put(Map.class, new MapSerializer());
|
|
||||||
serializers.put(byte[].class, new ByteArraySerializer());
|
|
||||||
serializers.put(Short.class, serializerClass(Opcodes.I2S));
|
|
||||||
serializers.put(Byte.class, serializerClass(Opcodes.I2B));
|
|
||||||
serializers.put(Type.class, (Serializer<Type>) e -> { // ow.Type == java.lang.Class in LDC
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(new LdcInsnNode(e));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Boolean.class, (Serializer<Boolean>) e -> {
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(new InsnNode(e ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Character.class, (Serializer<Character>) e -> {
|
|
||||||
InsnList ret = new InsnList();
|
|
||||||
ret.add(NodeUtils.push((int) e));
|
|
||||||
ret.add(new InsnNode(Opcodes.I2C));
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, Object> values;
|
private static final List<Class<?>> primitiveLDCClasses = Arrays.asList(java.lang.Integer.class, java.lang.Long.class,
|
||||||
|
java.lang.Float.class, java.lang.Double.class, java.lang.String.class);
|
||||||
|
private static final String INJECTED_FIELD_DESC = Type.getDescriptor(LauncherInject.class);
|
||||||
|
private static final String INJECTED_CONSTRUCTOR_DESC = Type.getDescriptor(LauncherInjectionConstructor.class);
|
||||||
|
private static final List<String> primitiveLDCDescriptors = Arrays.asList(Type.INT_TYPE.getDescriptor(), Type.DOUBLE_TYPE.getDescriptor(),
|
||||||
|
Type.FLOAT_TYPE.getDescriptor(), Type.LONG_TYPE.getDescriptor(), Type.getDescriptor(String.class));
|
||||||
|
|
||||||
public InjectClassAcceptor(Map<String, Object> values) {
|
private static void visit(ClassNode classNode, Map<String, Object> values) {
|
||||||
this.values = 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 void visit(ClassNode classNode, Map<String, Object> values) {
|
private static final Map<Class<?>, Serializer<?>> serializers;
|
||||||
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) {
|
static {
|
||||||
AnnotationNode valueAnnotation = field.invisibleAnnotations != null ? field.invisibleAnnotations.stream()
|
serializers = new HashMap<>();
|
||||||
.filter(annotation -> INJECTED_FIELD_DESC.equals(annotation.desc)).findFirst()
|
serializers.put(List.class, new ListSerializer());
|
||||||
.orElse(null) : null;
|
serializers.put(Map.class, new MapSerializer());
|
||||||
if (valueAnnotation == null) {
|
serializers.put(byte[].class, new ByteArraySerializer());
|
||||||
return;
|
serializers.put(Short.class, serializerClass(Opcodes.I2S));
|
||||||
}
|
serializers.put(Byte.class, serializerClass(Opcodes.I2B));
|
||||||
field.invisibleAnnotations.remove(valueAnnotation);
|
serializers.put(Type.class, (Serializer<Type>) e -> { // ow.Type == java.lang.Class in LDC
|
||||||
AtomicReference<String> valueName = new AtomicReference<>(null);
|
InsnList ret = new InsnList();
|
||||||
valueAnnotation.accept(new AnnotationVisitor(Opcodes.ASM7) {
|
ret.add(new LdcInsnNode(e));
|
||||||
@Override
|
return ret;
|
||||||
public void visit(final String name, final Object value) {
|
});
|
||||||
if ("value".equals(name)) {
|
serializers.put(Boolean.class, (Serializer<Boolean>) e -> {
|
||||||
if (value.getClass() != String.class)
|
InsnList ret = new InsnList();
|
||||||
throw new IllegalArgumentException(
|
ret.add(new InsnNode(e ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
|
||||||
"Invalid annotation with value class %s".formatted(field.getClass().getName()));
|
return ret;
|
||||||
valueName.set(value.toString());
|
});
|
||||||
}
|
serializers.put(Character.class, (Serializer<Character>) e -> {
|
||||||
}
|
InsnList ret = new InsnList();
|
||||||
});
|
ret.add(NodeUtils.push((int) e));
|
||||||
if (valueName.get() == null) {
|
ret.add(new InsnNode(Opcodes.I2C));
|
||||||
throw new IllegalArgumentException("Annotation should always contains 'value' key");
|
return ret;
|
||||||
}
|
});
|
||||||
if (!values.containsKey(valueName.get())) {
|
serializers.put(Enum.class, (Serializer<Enum>) NodeUtils::makeValueEnumGetter);
|
||||||
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>() {
|
||||||
InsnList ret = new InsnList();
|
@Override
|
||||||
ret.add(NodeUtils.push(value.intValue()));
|
public InsnList serialize(Number value) {
|
||||||
ret.add(new InsnNode(opcode));
|
InsnList ret = new InsnList();
|
||||||
return ret;
|
ret.add(NodeUtils.push(((Number) value).intValue()));
|
||||||
};
|
ret.add(new InsnNode(opcode));
|
||||||
}
|
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) {
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
|
insnList.add(new InsnNode(Opcodes.ACONST_NULL));
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
if (primitiveLDCClasses.contains(value.getClass())) {
|
if (primitiveLDCClasses.contains(value.getClass())) {
|
||||||
InsnList insnList = new InsnList();
|
InsnList insnList = new InsnList();
|
||||||
insnList.add(new LdcInsnNode(value));
|
insnList.add(new LdcInsnNode(value));
|
||||||
return insnList;
|
return insnList;
|
||||||
}
|
}
|
||||||
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
|
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
|
||||||
if (serializerEntry.getKey().isInstance(value)) {
|
if (serializerEntry.getKey().isInstance(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) {
|
private static class ListSerializer implements Serializer<List> {
|
||||||
if (value == null) return true;
|
@Override
|
||||||
if (primitiveLDCClasses.contains(value.getClass())) return true;
|
public InsnList serialize(List value) {
|
||||||
for (Map.Entry<Class<?>, Serializer<?>> serializerEntry : serializers.entrySet()) {
|
InsnList insnList = new InsnList();
|
||||||
if (serializerEntry.getKey().isInstance(value)) {
|
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
|
||||||
return true;
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
}
|
insnList.add(NodeUtils.push(value.size()));
|
||||||
}
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
|
||||||
return false;
|
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
|
||||||
}
|
for (Object object : value) {
|
||||||
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
|
insnList.add(serializeValue(object));
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
|
||||||
|
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
|
||||||
|
insnList.add(new InsnNode(Opcodes.POP));
|
||||||
|
}
|
||||||
|
return insnList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private static class MapSerializer implements Serializer<Map> {
|
||||||
public void transform(ClassNode classNode, String className, BuildContext context) {
|
@Override
|
||||||
visit(classNode, values);
|
public InsnList serialize(Map value) {
|
||||||
}
|
InsnList insnList = new InsnList();
|
||||||
|
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
|
||||||
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
|
||||||
|
Type.getMethodDescriptor(Type.VOID_TYPE), false));
|
||||||
|
for (Object entryObject : value.entrySet()) {
|
||||||
|
Map.Entry entry = (Map.Entry) entryObject;
|
||||||
|
insnList.add(new InsnNode(Opcodes.DUP));
|
||||||
|
insnList.add(serializeValue(entry.getKey()));
|
||||||
|
insnList.add(serializeValue(entry.getValue()));
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "put",
|
||||||
|
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)),
|
||||||
|
true));
|
||||||
|
insnList.add(new InsnNode(Opcodes.POP));
|
||||||
|
}
|
||||||
|
return insnList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
private static class ByteArraySerializer implements Serializer<byte[]> {
|
||||||
private interface Serializer<T> {
|
@Override
|
||||||
InsnList serialize(T value);
|
public InsnList serialize(byte[] value) {
|
||||||
}
|
InsnList insnList = new InsnList();
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
|
||||||
|
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
|
||||||
|
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
|
||||||
|
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
|
||||||
|
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
|
||||||
|
false));
|
||||||
|
return insnList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ListSerializer implements Serializer<List> {
|
@Override
|
||||||
@Override
|
public void transform(ClassNode classNode, String className, BuildContext context) {
|
||||||
public InsnList serialize(List value) {
|
visit(classNode, values);
|
||||||
InsnList insnList = new InsnList();
|
}
|
||||||
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(ArrayList.class)));
|
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
|
||||||
insnList.add(NodeUtils.push(value.size()));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(ArrayList.class), "<init>",
|
|
||||||
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false));
|
|
||||||
for (Object object : value) {
|
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
|
||||||
insnList.add(serializeValue(object));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add",
|
|
||||||
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)), true));
|
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
|
||||||
}
|
|
||||||
return insnList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MapSerializer implements Serializer<Map> {
|
|
||||||
@Override
|
|
||||||
public InsnList serialize(Map value) {
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(value.getClass())));
|
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(value.getClass()), "<init>",
|
|
||||||
Type.getMethodDescriptor(Type.VOID_TYPE), false));
|
|
||||||
for (Object entryObject : value.entrySet()) {
|
|
||||||
Map.Entry entry = (Map.Entry) entryObject;
|
|
||||||
insnList.add(new InsnNode(Opcodes.DUP));
|
|
||||||
insnList.add(serializeValue(entry.getKey()));
|
|
||||||
insnList.add(serializeValue(entry.getValue()));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "put",
|
|
||||||
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class), Type.getType(Object.class)),
|
|
||||||
true));
|
|
||||||
insnList.add(new InsnNode(Opcodes.POP));
|
|
||||||
}
|
|
||||||
return insnList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ByteArraySerializer implements Serializer<byte[]> {
|
|
||||||
@Override
|
|
||||||
public InsnList serialize(byte[] value) {
|
|
||||||
InsnList insnList = new InsnList();
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Base64.class),
|
|
||||||
"getDecoder", Type.getMethodDescriptor(Type.getType(Base64.Decoder.class)), false));
|
|
||||||
insnList.add(NodeUtils.getSafeStringInsnList(Base64.getEncoder().encodeToString(value)));
|
|
||||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Base64.Decoder.class),
|
|
||||||
"decode", Type.getMethodDescriptor(Type.getType(byte[].class), Type.getType(String.class)),
|
|
||||||
false));
|
|
||||||
return insnList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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<>();
|
||||||
|
|
||||||
|
@ -235,9 +238,9 @@ else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InsnList makeValueEnumGetter(@SuppressWarnings("rawtypes") Enum u) {
|
public static InsnList makeValueEnumGetter(@SuppressWarnings("rawtypes") Enum u) {
|
||||||
InsnList ret = new InsnList();
|
InsnList ret = new InsnList();
|
||||||
Type e = Type.getType(u.getClass());
|
Type e = Type.getType(u.getClass());
|
||||||
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
|
ret.add(new FieldInsnNode(Opcodes.GETSTATIC, e.getInternalName(), u.name(), e.getDescriptor()));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -1,113 +1,66 @@
|
||||||
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.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 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) {
|
||||||
}
|
this.provider = provider;
|
||||||
|
this.handler = handler;
|
||||||
public AuthProviderPair(AuthCoreProvider core, TextureProvider textureProvider) {
|
|
||||||
this.core = core;
|
|
||||||
this.textureProvider = textureProvider;
|
this.textureProvider = textureProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getFeatures(Class<?> clazz) {
|
|
||||||
Set<String> list = new HashSet<>();
|
|
||||||
getFeatures(clazz, list);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getFeatures() {
|
|
||||||
return features;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getFeatures(Class<?> clazz, Set<String> list) {
|
|
||||||
Feature[] features = clazz.getAnnotationsByType(Feature.class);
|
|
||||||
for (Feature feature : features) {
|
|
||||||
list.add(feature.value());
|
|
||||||
}
|
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
|
||||||
if (superClass != null && superClass != Object.class) {
|
|
||||||
getFeatures(superClass, list);
|
|
||||||
}
|
|
||||||
Class<?>[] interfaces = clazz.getInterfaces();
|
|
||||||
for (Class<?> i : interfaces) {
|
|
||||||
getFeatures(i, list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T isSupport(Class<T> clazz) {
|
|
||||||
if (core == null) return null;
|
|
||||||
T result = core.isSupport(clazz);
|
|
||||||
if (result == null && mixes != null) {
|
|
||||||
for(var m : mixes.values()) {
|
|
||||||
result = m.isSupport(clazz);
|
|
||||||
if(result != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(LaunchServer srv, String name) {
|
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) {
|
provider.init(srv);
|
||||||
for(var m : mixes.values()) {
|
handler.init(srv);
|
||||||
m.init(srv, core);
|
|
||||||
getFeatures(m.getClass(), features);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
public void link(LaunchServer srv)
|
||||||
public void link(LaunchServer srv) {
|
{
|
||||||
links.forEach((k, v) -> {
|
links.forEach((k,v) -> {
|
||||||
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
AuthProviderPair pair = srv.config.getAuthProviderPair(v);
|
||||||
if (pair == null) {
|
if(pair == null)
|
||||||
throw new NullPointerException("Auth %s link failed. Pair %s not found".formatted(name, v));
|
{
|
||||||
|
throw new NullPointerException(String.format("Auth %s link failed. Pair %s not found", name, v));
|
||||||
}
|
}
|
||||||
if ("core".equals(k)) {
|
if("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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core;
|
|
||||||
|
|
||||||
public interface UserSession {
|
|
||||||
String getID();
|
|
||||||
|
|
||||||
User getUser();
|
|
||||||
|
|
||||||
String getMinecraftAccessToken();
|
|
||||||
|
|
||||||
long getExpireIn();
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
|
||||||
|
|
||||||
public interface AuthSupport {
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.session;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public interface UserSessionSupportProperties {
|
|
||||||
Map<String, String> getProperties();
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.interfaces.user;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public interface UserSupportProperties {
|
|
||||||
Map<String, String> getProperties();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package pro.gravit.launchserver.auth.core.openid;
|
|
||||||
|
|
||||||
public record TokenResponse(String accessToken, long accessTokenExpiresIn,
|
|
||||||
String refreshToken, long refreshTokenExpiresIn) {
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 "";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +1,22 @@
|
||||||
package pro.gravit.launchserver.auth.protect;
|
package pro.gravit.launchserver.auth.protect;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtParser;
|
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
|
||||||
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.auth.protect.interfaces.SecureProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
import pro.gravit.launchserver.socket.response.secure.HardwareReportResponse;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.util.Base64;
|
public class AdvancedProtectHandler extends ProtectHandler implements SecureProtectHandler {
|
||||||
import java.util.Date;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
@Override
|
||||||
|
public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||||
|
return (context.authType == AuthResponse.ConnectTypes.CLIENT) && context.client.checkSign;
|
||||||
|
}
|
||||||
|
|
||||||
public class AdvancedProtectHandler extends StdProtectHandler implements SecureProtectHandler, HardwareProtectHandler, JoinServerProtectHandler {
|
@Override
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
public void checkLaunchServerLicense() {
|
||||||
public boolean enableHardwareFeature;
|
|
||||||
private transient LaunchServer server;
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
|
public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event) {
|
||||||
|
@ -38,152 +27,4 @@ public GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoReq
|
||||||
public boolean allowGetSecureLevelInfo(Client client) {
|
public boolean allowGetSecureLevelInfo(Client client) {
|
||||||
return client.checkSign;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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 {
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean allowJoinServer(Client client) {
|
public void checkLaunchServerLicense() {
|
||||||
return true;
|
// None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -20,16 +18,7 @@ public static void registerHandlers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
|
public abstract boolean allowGetAccessToken(AuthResponse.AuthContext context);
|
||||||
public boolean allowJoinServer(Client client) {
|
|
||||||
return client.isAuth && client.type == AuthResponse.ConnectTypes.CLIENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(LaunchServer server) {
|
public abstract void checkLaunchServerLicense(); //Выдает SecurityException при ошибке проверки лицензии
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
//public abstract
|
//public abstract
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +1,48 @@
|
||||||
package pro.gravit.launchserver.auth.protect;
|
package pro.gravit.launchserver.auth.protect;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import pro.gravit.launcher.profiles.ClientProfile;
|
||||||
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.auth.protect.interfaces.ProfilesProtectHandler;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
|
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
|
||||||
private transient final Logger logger = LogManager.getLogger();
|
|
||||||
public Map<String, List<String>> profileWhitelist = new HashMap<>();
|
public Map<String, List<String>> profileWhitelist = new HashMap<>();
|
||||||
public List<String> allowUpdates = new ArrayList<>();
|
public List<String> allowUpdates = new ArrayList<>();
|
||||||
|
|
||||||
@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) && context.client.checkSign;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public boolean canGetProfile(ClientProfile profile, Client client) {
|
public boolean canGetProfile(ClientProfile profile, Client client) {
|
||||||
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
|
return canChangeProfile(profile, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canChangeProfile(ClientProfile profile, Client client) {
|
public boolean canChangeProfile(ClientProfile profile, Client client) {
|
||||||
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
|
return client.isAuth && client.username != null && isWhitelisted(profile.getTitle(), client.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canGetUpdates(String updatesDirName, Client client) {
|
public boolean canGetUpdates(String updatesDirName, Client client) {
|
||||||
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
|
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) {
|
public boolean isWhitelisted(String profileTitle, String username)
|
||||||
if (client.permissions != null) {
|
{
|
||||||
String permByUUID = property.formatted(profile.getUUID());
|
List<String> allowedUsername = profileWhitelist.get(profileTitle);
|
||||||
if (client.permissions.hasPerm(permByUUID)) {
|
if(allowedUsername == null) return true;
|
||||||
return true;
|
return allowedUsername.contains(username);
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,23 @@
|
||||||
package pro.gravit.launchserver.auth.protect.interfaces;
|
package pro.gravit.launchserver.auth.protect.interfaces;
|
||||||
|
|
||||||
import pro.gravit.launcher.base.profiles.ClientProfile;
|
import pro.gravit.launcher.profiles.ClientProfile;
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
|
|
||||||
public interface ProfilesProtectHandler {
|
public interface ProfilesProtectHandler {
|
||||||
default boolean canGetProfiles(Client client) {
|
default boolean canGetProfiles(Client client)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
default boolean canGetProfile(ClientProfile profile, Client client)
|
||||||
default boolean canGetProfile(ClientProfile profile, Client client) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
default boolean canChangeProfile(ClientProfile profile, Client client)
|
||||||
default boolean canChangeProfile(ClientProfile profile, Client client) {
|
{
|
||||||
return client.isAuth;
|
return client.isAuth;
|
||||||
}
|
}
|
||||||
|
default boolean canGetUpdates(String updatesDirName, Client client)
|
||||||
default boolean canGetUpdates(String updatesDirName, Client client) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package pro.gravit.launchserver.auth.protect.interfaces;
|
package pro.gravit.launchserver.auth.protect.interfaces;
|
||||||
|
|
||||||
import pro.gravit.launcher.base.events.request.GetSecureLevelInfoRequestEvent;
|
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
|
||||||
import pro.gravit.launcher.base.events.request.SecurityReportRequestEvent;
|
import pro.gravit.launcher.events.request.SecurityReportRequestEvent;
|
||||||
import pro.gravit.launcher.base.events.request.VerifySecureLevelKeyRequestEvent;
|
|
||||||
import pro.gravit.launchserver.socket.Client;
|
import pro.gravit.launchserver.socket.Client;
|
||||||
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
import pro.gravit.launchserver.socket.response.secure.SecurityReportResponse;
|
||||||
import pro.gravit.utils.helper.SecurityHelper;
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
@ -13,27 +12,21 @@
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
public interface SecureProtectHandler {
|
public interface SecureProtectHandler {
|
||||||
default byte[] generateSecureLevelKey() {
|
default byte[] generateSecureLevelKey()
|
||||||
|
{
|
||||||
return SecurityHelper.randomBytes(128);
|
return SecurityHelper.randomBytes(128);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void verifySecureLevelKey(byte[] publicKey, byte[] data, byte[] signature) throws InvalidKeySpecException, SignatureException {
|
default void verifySecureLevelKey(byte[] publicKey, byte[] data, byte[] signature) throws InvalidKeySpecException, SignatureException {
|
||||||
if (publicKey == null || signature == null) throw new InvalidKeySpecException();
|
if(publicKey == null || signature == null) throw new InvalidKeySpecException();
|
||||||
ECPublicKey pubKey = SecurityHelper.toPublicECDSAKey(publicKey);
|
ECPublicKey pubKey = SecurityHelper.toPublicECKey(publicKey);
|
||||||
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
|
Signature sign = SecurityHelper.newECVerifySignature(pubKey);
|
||||||
sign.update(data);
|
sign.update(data);
|
||||||
sign.verify(signature);
|
sign.verify(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
|
GetSecureLevelInfoRequestEvent onGetSecureLevelInfo(GetSecureLevelInfoRequestEvent event);
|
||||||
|
|
||||||
boolean allowGetSecureLevelInfo(Client client);
|
boolean allowGetSecureLevelInfo(Client client);
|
||||||
|
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client)
|
||||||
default SecurityReportRequestEvent onSecurityReport(SecurityReportResponse report, Client client) {
|
{
|
||||||
return new SecurityReportRequestEvent();
|
return new SecurityReportRequestEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
default VerifySecureLevelKeyRequestEvent onSuccessVerify(Client client) {
|
|
||||||
return new VerifySecureLevelKeyRequestEvent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
|
||||||
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.utils.ProviderMap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class AuthProvider implements AutoCloseable {
|
||||||
|
public static final ProviderMap<AuthProvider> providers = new ProviderMap<>("AuthProvider");
|
||||||
|
private static boolean registredProv = false;
|
||||||
|
protected transient LaunchServer srv = null;
|
||||||
|
|
||||||
|
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getFirstAuthType() {
|
||||||
|
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.PASSWORD;
|
||||||
|
}
|
||||||
|
public GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType getSecondAuthType() {
|
||||||
|
return GetAvailabilityAuthRequestEvent.AuthAvailability.AuthType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthProviderResult authError(String message) throws AuthException {
|
||||||
|
throw new AuthException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerProviders() {
|
||||||
|
if (!registredProv) {
|
||||||
|
providers.register("null", NullAuthProvider.class);
|
||||||
|
providers.register("accept", AcceptAuthProvider.class);
|
||||||
|
providers.register("reject", RejectAuthProvider.class);
|
||||||
|
providers.register("mysql", MySQLAuthProvider.class);
|
||||||
|
providers.register("postgresql", PostgreSQLAuthProvider.class);
|
||||||
|
providers.register("request", RequestAuthProvider.class);
|
||||||
|
providers.register("json", JsonAuthProvider.class);
|
||||||
|
providers.register("hibernate", HibernateAuthProvider.class);
|
||||||
|
registredProv = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the username and password
|
||||||
|
*
|
||||||
|
* @param login user login
|
||||||
|
* @param password user password
|
||||||
|
* @param ip user ip
|
||||||
|
* @return player privileges, effective username and authorization token
|
||||||
|
* @throws Exception Throws an exception {@link AuthException} {@link pro.gravit.utils.HookException} if the verification script returned a meaningful error
|
||||||
|
* In other cases, throwing an exception indicates a serious error
|
||||||
|
*/
|
||||||
|
public abstract AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception;
|
||||||
|
|
||||||
|
public void preAuth(String login, AuthRequest.AuthPasswordInterface password, String ip) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void close() throws IOException;
|
||||||
|
|
||||||
|
public void init(LaunchServer srv) {
|
||||||
|
this.srv = srv;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.ClientPermissions;
|
||||||
|
|
||||||
|
|
||||||
|
public class AuthProviderResult {
|
||||||
|
public final String username;
|
||||||
|
public final String accessToken;
|
||||||
|
public final ClientPermissions permissions;
|
||||||
|
|
||||||
|
public AuthProviderResult(String username, String accessToken) {
|
||||||
|
this.username = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
permissions = ClientPermissions.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthProviderResult(String username, String accessToken, ClientPermissions permissions) {
|
||||||
|
this.username = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper.DigestAlgorithm;
|
||||||
|
|
||||||
|
public abstract class DigestAuthProvider extends AuthProvider {
|
||||||
|
private DigestAlgorithm digest;
|
||||||
|
|
||||||
|
|
||||||
|
protected final void verifyDigest(String validDigest, String password) throws AuthException {
|
||||||
|
boolean valid;
|
||||||
|
if (digest == DigestAlgorithm.PLAIN)
|
||||||
|
valid = password.equals(validDigest);
|
||||||
|
else if (validDigest == null)
|
||||||
|
valid = false;
|
||||||
|
else {
|
||||||
|
byte[] actualDigest = SecurityHelper.digest(digest, password);
|
||||||
|
valid = SecurityHelper.toHex(actualDigest).equals(validDigest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify is valid
|
||||||
|
if (!valid)
|
||||||
|
authError("Incorrect username or password");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.dao.User;
|
||||||
|
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
public class HibernateAuthProvider extends AuthProvider {
|
||||||
|
public boolean autoReg;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception {
|
||||||
|
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||||
|
User user = srv.config.dao.userDAO.findByUsername(login);
|
||||||
|
if (user == null && autoReg) {
|
||||||
|
AuthHookManager.RegContext context = new AuthHookManager.RegContext(login, ((AuthPlainPassword) password).password, ip, false);
|
||||||
|
if (srv.authHookManager.registraion.hook(context)) {
|
||||||
|
//user = srv.config.dao.userService.registerNewUser(login, ((AuthPlainPassword) password).password); //TODO: FIX
|
||||||
|
} else {
|
||||||
|
throw new AuthException("Registration canceled. Try again later");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (user == null || !user.verifyPassword(((AuthPlainPassword) password).password)) {
|
||||||
|
if (user == null) throw new AuthException("Username incorrect");
|
||||||
|
else throw new AuthException("Username or password incorrect");
|
||||||
|
}
|
||||||
|
return new AuthProviderResult(login, SecurityHelper.randomStringToken(), user.getPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import pro.gravit.launcher.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launcher.HTTPRequest;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public final class JsonAuthProvider extends AuthProvider {
|
||||||
|
private static final Gson gson = new Gson();
|
||||||
|
private URL url;
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
public static class authResult {
|
||||||
|
String username;
|
||||||
|
String error;
|
||||||
|
long permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class authRequest {
|
||||||
|
public authRequest(String username, String password, String ip) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.ip = ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public authRequest(String username, String password, String ip, String apiKey) {
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.ip = ip;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String username;
|
||||||
|
final String password;
|
||||||
|
final String ip;
|
||||||
|
String apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
|
||||||
|
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||||
|
authRequest authRequest = new authRequest(login, ((AuthPlainPassword) password).password, ip, apiKey);
|
||||||
|
JsonElement request = gson.toJsonTree(authRequest);
|
||||||
|
JsonElement content = HTTPRequest.jsonRequest(request, url);
|
||||||
|
if (!content.isJsonObject())
|
||||||
|
return authError("Authentication server response is malformed");
|
||||||
|
|
||||||
|
authResult result = gson.fromJson(content, authResult.class);
|
||||||
|
if (result.username != null)
|
||||||
|
return new AuthProviderResult(result.username, SecurityHelper.randomStringToken(), new ClientPermissions(result.permissions));
|
||||||
|
else if (result.error != null)
|
||||||
|
return authError(result.error);
|
||||||
|
else
|
||||||
|
return authError("Authentication server response is malformed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.ClientPermissions;
|
||||||
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
|
import pro.gravit.launchserver.auth.AuthException;
|
||||||
|
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||||
|
import pro.gravit.utils.helper.CommonHelper;
|
||||||
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public final class MySQLAuthProvider extends AuthProvider {
|
||||||
|
private MySQLSourceConfig mySQLHolder;
|
||||||
|
private String query;
|
||||||
|
private String message;
|
||||||
|
private String[] queryParams;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(LaunchServer srv) {
|
||||||
|
super.init(srv);
|
||||||
|
if (query == null) LogHelper.error("[Verify][AuthProvider] query cannot be null");
|
||||||
|
if (message == null) LogHelper.error("[Verify][AuthProvider] message cannot be null");
|
||||||
|
if (mySQLHolder == null) LogHelper.error("[Verify][AuthProvider] mySQLHolder cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws SQLException, AuthException {
|
||||||
|
if (!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||||
|
try (Connection c = mySQLHolder.getConnection()) {
|
||||||
|
PreparedStatement s = c.prepareStatement(query);
|
||||||
|
String[] replaceParams = {"login", login, "password", ((AuthPlainPassword) password).password, "ip", ip};
|
||||||
|
for (int i = 0; i < queryParams.length; i++)
|
||||||
|
s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams));
|
||||||
|
|
||||||
|
// Execute SQL query
|
||||||
|
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||||
|
try (ResultSet set = s.executeQuery()) {
|
||||||
|
return set.next() ? new AuthProviderResult(set.getString(1), SecurityHelper.randomStringToken(), new ClientPermissions(set.getLong(2))) : authError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
mySQLHolder.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package pro.gravit.launchserver.auth.provider;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||||
|
import pro.gravit.utils.helper.VerifyHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class NullAuthProvider extends AuthProvider {
|
||||||
|
private volatile AuthProvider provider;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception {
|
||||||
|
return getProvider().auth(login, password, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
AuthProvider provider = this.provider;
|
||||||
|
if (provider != null)
|
||||||
|
provider.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthProvider getProvider() {
|
||||||
|
return VerifyHelper.verify(provider, Objects::nonNull, "Backend auth provider wasn't set");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setBackend(AuthProvider provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue