mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-23 00:51:01 +03:00
Merge branch 'GravitLauncher:master' into master
This commit is contained in:
commit
8f20cbe104
109 changed files with 1410 additions and 1250 deletions
|
@ -24,7 +24,7 @@
|
|||
|
||||
**Основные правила:**
|
||||
|
||||
1. Все коммиты должны быть на русском языке.
|
||||
1. Все коммиты должны быть на английском языке.
|
||||
2. Запрещено использовать прошедшее время.
|
||||
3. Обязательно должен быть использован префикс.
|
||||
4. В конце не должно быть лишнего знака препинания.
|
||||
|
@ -38,10 +38,10 @@
|
|||
|
||||
| Префикс | Значение | Пример |
|
||||
| ------- | -------- | ------ |
|
||||
| **[FIX]** | Всё, что касается исправления багов | [FIX] Баг с неудачной авторизацией |
|
||||
| **[DOCS]** | Всё, что касается документации | [DOCS] Документирование API авторизации |
|
||||
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA при авторизации |
|
||||
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Опечатки в модуле авторизации |
|
||||
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Переход на EDA в модуле авторизации |
|
||||
| **[TEST]** | Всё, что касается тестирования | [TEST] Покрытие модуля авторизации тестами |
|
||||
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Подключение Travis CI |
|
||||
| **[FIX]** | Всё, что касается исправления багов | [FIX] Bug with failed authorization |
|
||||
| **[DOCS]** | Всё, что касается документации | [DOCS] Documenting Authorization API |
|
||||
| **[FEATURE]** | Всё, что касается новых возможностей | [FEATURE] 2FA on authorization |
|
||||
| **[STYLE]** | Всё, что касается опечаток и форматирования | [STYLE] Typos in the authorization module |
|
||||
| **[REFACTOR]** | Всё, что касается рефакторинга | [REFACTOR] Switching to EDA in the authorization module |
|
||||
| **[TEST]** | Всё, что касается тестирования | [TEST] Coverage of the authorization module with tests |
|
||||
| **[ANY]** | Всё, что не подходит к предыдущему. | [ANY] Connecting Travis CI |
|
||||
|
|
|
@ -186,7 +186,7 @@ task dumpClientLibs(type: Copy) {
|
|||
pom {
|
||||
name = 'GravitLauncher LaunchServer API'
|
||||
description = 'GravitLauncher LaunchServer Module API'
|
||||
url = 'https://launcher.gravit.pro'
|
||||
url = 'https://gravitlauncher.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU General Public License, Version 3.0'
|
||||
|
@ -209,7 +209,7 @@ task dumpClientLibs(type: Copy) {
|
|||
scm {
|
||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||
url = 'https://launcher.gravit.pro/'
|
||||
url = 'https://gravitlauncher.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,25 +17,6 @@ public class HttpRequester {
|
|||
public HttpRequester() {
|
||||
}
|
||||
|
||||
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 <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
|
||||
return new SimpleErrorHandler<>(clazz);
|
||||
}
|
||||
|
@ -78,6 +59,26 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
|
|||
return HttpHelper.send(httpClient, request, makeEH(clazz));
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launcher.events.RequestEvent;
|
||||
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
|
||||
import pro.gravit.launcher.managers.ConfigManager;
|
||||
import pro.gravit.launcher.modules.events.ClosePhase;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
|
@ -19,7 +21,9 @@
|
|||
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
||||
import pro.gravit.launchserver.modules.events.*;
|
||||
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler;
|
||||
import pro.gravit.launchserver.socket.response.auth.ProfilesResponse;
|
||||
import pro.gravit.launchserver.socket.response.auth.RestoreResponse;
|
||||
import pro.gravit.utils.command.Command;
|
||||
import pro.gravit.utils.command.CommandHandler;
|
||||
|
@ -63,6 +67,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
|
|||
* The path to the folder with compile-only libraries for the launcher
|
||||
*/
|
||||
public final Path launcherLibrariesCompile;
|
||||
public final Path launcherPack;
|
||||
/**
|
||||
* The path to the folder with updates/webroot
|
||||
*/
|
||||
|
@ -133,6 +138,10 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
|
|||
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
|
||||
launcherLibraries = directories.launcherLibrariesDir;
|
||||
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
|
||||
launcherPack = directories.launcherPackDir;
|
||||
if(!Files.isDirectory(launcherPack)) {
|
||||
Files.createDirectories(launcherPack);
|
||||
}
|
||||
|
||||
config.setLaunchServer(this);
|
||||
|
||||
|
@ -374,6 +383,24 @@ public void syncProfilesDir() throws IOException {
|
|||
// Sort and set new profiles
|
||||
newProfies.sort(Comparator.comparing(a -> a));
|
||||
profilesList = Set.copyOf(newProfies);
|
||||
if (config.netty.sendProfileUpdatesEvent) {
|
||||
sendUpdateProfilesEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdateProfilesEvent() {
|
||||
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
|
||||
return;
|
||||
}
|
||||
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
|
||||
Client client = handler.getClient();
|
||||
if (client == null || !client.isAuth) {
|
||||
return;
|
||||
}
|
||||
ProfilesRequestEvent event = new ProfilesRequestEvent(ProfilesResponse.getListVisibleProfiles(this, client));
|
||||
event.requestUUID = RequestEvent.eventUUID;
|
||||
handler.service.sendObject(ch, event);
|
||||
});
|
||||
}
|
||||
|
||||
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
|
||||
|
@ -464,11 +491,12 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
|||
public static class LaunchServerDirectories {
|
||||
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
|
||||
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
|
||||
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", KEY_NAME = ".keys";
|
||||
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys";
|
||||
public Path updatesDir;
|
||||
public Path profilesDir;
|
||||
public Path launcherLibrariesDir;
|
||||
public Path launcherLibrariesCompileDir;
|
||||
public Path launcherPackDir;
|
||||
public Path keyDirectory;
|
||||
public Path dir;
|
||||
public Path trustStore;
|
||||
|
@ -481,6 +509,8 @@ public void collect() {
|
|||
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
|
||||
if (launcherLibrariesCompileDir == null)
|
||||
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
|
||||
if(launcherPackDir == null)
|
||||
launcherPackDir = getPath(LAUNCHERPACK_NAME);
|
||||
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
|
||||
if (tmpDir == null)
|
||||
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
|
||||
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
|
||||
import pro.gravit.launchserver.socket.WebSocketService;
|
||||
import pro.gravit.utils.Version;
|
||||
import pro.gravit.utils.command.CommandHandler;
|
||||
import pro.gravit.utils.command.JLineCommandHandler;
|
||||
import pro.gravit.utils.command.StdCommandHandler;
|
||||
|
@ -28,14 +29,12 @@
|
|||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
|
||||
public class LaunchServerStarter {
|
||||
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
|
||||
|
@ -89,6 +88,7 @@ public static void main(String[] args) throws Exception {
|
|||
modulesManager.initModules(null);
|
||||
registerAll();
|
||||
initGson(modulesManager);
|
||||
printExperimentalBranch();
|
||||
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
|
||||
configFile = dir.resolve("LaunchServer.conf");
|
||||
} else {
|
||||
|
@ -148,24 +148,34 @@ public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
|
|||
|
||||
@Override
|
||||
public void writeConfig(LaunchServerConfig config) throws IOException {
|
||||
try (Writer writer = IOHelper.newWriter(configFile)) {
|
||||
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");
|
||||
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 {
|
||||
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
|
||||
|
@ -206,6 +216,26 @@ public static void registerAll() {
|
|||
OptionalTrigger.registerProviders();
|
||||
}
|
||||
|
||||
private static void printExperimentalBranch() {
|
||||
try(Reader reader = IOHelper.newReader(IOHelper.getResourceURL("experimental-build.json"))) {
|
||||
ExperimentalBuild info = Launcher.gsonManager.configGson.fromJson(reader, ExperimentalBuild.class);
|
||||
if(info.features == null || info.features.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
logger.warn("This is experimental build. Please do not use this in production");
|
||||
logger.warn("Experimental features: [{}]", String.join(",", info.features));
|
||||
for(var e : info.info) {
|
||||
logger.warn(e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.warn("Build information not found");
|
||||
}
|
||||
}
|
||||
|
||||
record ExperimentalBuild(List<String> features, List<String> info) {
|
||||
|
||||
}
|
||||
|
||||
public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
|
||||
if (IOHelper.isFile(configFile))
|
||||
return;
|
||||
|
|
|
@ -22,6 +22,7 @@ public final class AuthProviderPair {
|
|||
public transient String name;
|
||||
public transient Set<String> features;
|
||||
public String displayName;
|
||||
public boolean visible = true;
|
||||
private transient boolean warnOAuthShow = false;
|
||||
|
||||
public AuthProviderPair() {
|
||||
|
@ -38,15 +39,6 @@ public static Set<String> getFeatures(Class<?> clazz) {
|
|||
return list;
|
||||
}
|
||||
|
||||
public void internalShowOAuthWarnMessage() {
|
||||
if(!warnOAuthShow) {
|
||||
if(!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
|
||||
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
|
||||
}
|
||||
warnOAuthShow = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void getFeatures(Class<?> clazz, Set<String> list) {
|
||||
Features features = clazz.getAnnotation(Features.class);
|
||||
if (features != null) {
|
||||
|
@ -64,6 +56,15 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
|
|||
}
|
||||
}
|
||||
|
||||
public void internalShowOAuthWarnMessage() {
|
||||
if (!warnOAuthShow) {
|
||||
if (!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
|
||||
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
|
||||
}
|
||||
warnOAuthShow = true;
|
||||
}
|
||||
}
|
||||
|
||||
public final <T> T isSupport(Class<T> clazz) {
|
||||
if (core == null) return null;
|
||||
T result = null;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class MySQLSourceConfig implements AutoCloseable {
|
||||
public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
|
||||
|
||||
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
||||
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class PostgreSQLSourceConfig implements AutoCloseable {
|
||||
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
|
||||
public static final int TIMEOUT = VerifyHelper.verifyInt(
|
||||
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
|
||||
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
|
||||
|
@ -28,8 +28,8 @@ public final class PostgreSQLSourceConfig implements AutoCloseable {
|
|||
private String database;
|
||||
|
||||
// Cache
|
||||
private DataSource source;
|
||||
private boolean hikari;
|
||||
private transient DataSource source;
|
||||
private transient boolean hikari;
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package pro.gravit.launchserver.auth;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public interface SQLSourceConfig {
|
||||
Connection getConnection() throws SQLException;
|
||||
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
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;
|
||||
|
||||
public abstract class AbstractSQLCoreProvider extends AuthCoreProvider {
|
||||
public transient Logger logger = LogManager.getLogger();
|
||||
public int expireSeconds = 3600;
|
||||
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 transient LaunchServer server;
|
||||
|
||||
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 new SQLUserSession(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, expireSeconds * 1000L, new SQLUserSession(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||
SQLUser SQLUser = (SQLUser) getUserByLogin(login);
|
||||
if (SQLUser == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if (context != null) {
|
||||
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
||||
if (plainPassword == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if (!passwordVerifier.check(SQLUser.password, plainPassword.password)) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
}
|
||||
SQLUserSession session = new SQLUserSession(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));
|
||||
if (minecraftAccess) {
|
||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||
updateAuth(SQLUser, minecraftAccessToken);
|
||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
} else {
|
||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
this.server = server;
|
||||
if (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 : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
|
||||
table, uuidColumn);
|
||||
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
|
||||
userInfoCols, table, usernameColumn);
|
||||
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
|
||||
table, accessTokenColumn, serverIDColumn, uuidColumn);
|
||||
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
|
||||
table, serverIDColumn, uuidColumn);
|
||||
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 : String.format("SELECT (%s) FROM %s WHERE %s=?",
|
||||
permissionsPermissionColumn, permissionsTable, permissionsUUIDColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String makeUserCols() {
|
||||
return String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
|
||||
}
|
||||
|
||||
protected boolean 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);
|
||||
return s.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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() throws IOException {
|
||||
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), requestPermissions(set.getString(uuidColumn))) : 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 {
|
||||
try (Connection c = getSQLConfig().getConnection()) {
|
||||
PreparedStatement s = c.prepareStatement(sql);
|
||||
s.setString(1, value);
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
try (ResultSet set = s.executeQuery()) {
|
||||
return constructUser(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 UUID uuid;
|
||||
protected String username;
|
||||
protected String accessToken;
|
||||
protected String serverId;
|
||||
protected String password;
|
||||
protected ClientPermissions permissions;
|
||||
|
||||
public SQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
this.accessToken = accessToken;
|
||||
this.serverId = serverId;
|
||||
this.password = password;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPermissions getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "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 long getExpireIn() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ public static void registerProviders() {
|
|||
providers.register("postgresql", PostgresSQLCoreProvider.class);
|
||||
providers.register("memory", MemoryAuthCoreProvider.class);
|
||||
providers.register("http", HttpAuthCoreProvider.class);
|
||||
providers.register("merge", MergeAuthCoreProvider.class);
|
||||
registredProviders = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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.ClientPermissions;
|
||||
|
@ -13,6 +14,7 @@
|
|||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRemoteClientAccess;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
|
||||
|
@ -25,9 +27,8 @@
|
|||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
|
||||
public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSupportHardware, AuthSupportRemoteClientAccess {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
private transient HttpRequester requester;
|
||||
public String bearerToken;
|
||||
public String getUserByUsernameUrl;
|
||||
public String getUserByLoginUrl;
|
||||
|
@ -49,6 +50,9 @@ public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSuppor
|
|||
public String getUsersByHardwareInfoUrl;
|
||||
public String banHardwareUrl;
|
||||
public String unbanHardwareUrl;
|
||||
public String apiUrl;
|
||||
public List<String> apiFeatures;
|
||||
private transient HttpRequester requester;
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
|
@ -229,14 +233,15 @@ public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey)
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@Override
|
||||
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
|
||||
if (getUsersByHardwareInfoUrl == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return requester.send(requester
|
||||
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), List.class).getOrThrow();
|
||||
return (List<User>) (List) requester.send(requester
|
||||
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), GetHardwareListResponse.class).getOrThrow().list;
|
||||
} catch (IOException e) {
|
||||
logger.error(e);
|
||||
return null;
|
||||
|
@ -267,12 +272,14 @@ public void unbanHardware(UserHardware hardware) {
|
|||
}
|
||||
}
|
||||
|
||||
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
|
||||
String oauthRefreshToken, long oauthExpire,
|
||||
HttpUserSession session) {
|
||||
public AuthManager.AuthReport toAuthReport() {
|
||||
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
|
||||
@Override
|
||||
public String getClientApiUrl() {
|
||||
return apiUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getClientApiFeatures() {
|
||||
return apiFeatures;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -293,6 +300,36 @@ public boolean joinServer(Client client, String username, String accessToken, St
|
|||
return result.isSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
requester = new HttpRequester();
|
||||
if (getUserByUsernameUrl == null) {
|
||||
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
|
||||
}
|
||||
if (getUserByUUIDUrl == null) {
|
||||
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
|
||||
}
|
||||
if (authorizeUrl == null) {
|
||||
throw new IllegalArgumentException("'authorizeUrl' can't be null");
|
||||
}
|
||||
if (checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
|
||||
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
|
||||
String oauthRefreshToken, long oauthExpire,
|
||||
HttpUserSession session) {
|
||||
public AuthManager.AuthReport toAuthReport() {
|
||||
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateServerIdRequest {
|
||||
public String username;
|
||||
public UUID uuid;
|
||||
|
@ -319,6 +356,10 @@ public static class GetAuthDetailsResponse {
|
|||
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
|
||||
}
|
||||
|
||||
public static class GetHardwareListResponse {
|
||||
public List<HttpUser> list;
|
||||
}
|
||||
|
||||
public static class JoinServerRequest {
|
||||
public String username;
|
||||
public String accessToken;
|
||||
|
@ -331,28 +372,6 @@ public JoinServerRequest(String username, String accessToken, String serverId) {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
requester = new HttpRequester();
|
||||
if(getUserByUsernameUrl == null) {
|
||||
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
|
||||
}
|
||||
if(getUserByUUIDUrl == null) {
|
||||
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
|
||||
}
|
||||
if(authorizeUrl == null) {
|
||||
throw new IllegalArgumentException("'authorizeUrl' can't be null");
|
||||
}
|
||||
if(checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
|
||||
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
public static class AuthorizeRequest {
|
||||
public String login;
|
||||
public AuthResponse.AuthContext context;
|
||||
|
@ -400,6 +419,106 @@ public HardwareRequest(byte[] key) {
|
|||
|
||||
}
|
||||
|
||||
public static class HttpUserSession implements UserSession {
|
||||
private String id;
|
||||
private HttpUser user;
|
||||
private long expireIn;
|
||||
|
||||
public HttpUserSession() {
|
||||
}
|
||||
|
||||
public HttpUserSession(String id, HttpUser user, long expireIn) {
|
||||
this.id = id;
|
||||
this.user = user;
|
||||
this.expireIn = expireIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpireIn() {
|
||||
return expireIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpUserSession{" +
|
||||
"id='" + id + '\'' +
|
||||
", user=" + user +
|
||||
", expireIn=" + expireIn +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpUserHardware implements UserHardware {
|
||||
private final HardwareReportRequest.HardwareInfo hardwareInfo;
|
||||
private final long id;
|
||||
private byte[] publicKey;
|
||||
private boolean banned;
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.publicKey = publicKey;
|
||||
this.id = id;
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.id = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, boolean banned) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.publicKey = publicKey;
|
||||
this.banned = banned;
|
||||
this.id = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public HttpUserHardware(long id) {
|
||||
this.id = id;
|
||||
this.hardwareInfo = null;
|
||||
}
|
||||
|
||||
@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 "HttpUserHardware{" +
|
||||
"hardwareInfo=" + hardwareInfo +
|
||||
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
|
||||
", id=" + id +
|
||||
", banned=" + banned +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
|
||||
private String username;
|
||||
private UUID uuid;
|
||||
|
@ -546,104 +665,4 @@ public UserHardware getHardware() {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpUserSession implements UserSession {
|
||||
private String id;
|
||||
private HttpUser user;
|
||||
private long expireIn;
|
||||
|
||||
public HttpUserSession() {
|
||||
}
|
||||
|
||||
public HttpUserSession(String id, HttpUser user, long expireIn) {
|
||||
this.id = id;
|
||||
this.user = user;
|
||||
this.expireIn = expireIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpireIn() {
|
||||
return expireIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HttpUserSession{" +
|
||||
"id='" + id + '\'' +
|
||||
", user=" + user +
|
||||
", expireIn=" + expireIn +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpUserHardware implements UserHardware {
|
||||
private final HardwareReportRequest.HardwareInfo hardwareInfo;
|
||||
private final long id;
|
||||
private byte[] publicKey;
|
||||
private boolean banned;
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.publicKey = publicKey;
|
||||
this.id = id;
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.id = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, boolean banned) {
|
||||
this.hardwareInfo = hardwareInfo;
|
||||
this.publicKey = publicKey;
|
||||
this.banned = banned;
|
||||
this.id = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public HttpUserHardware(long id) {
|
||||
this.id = id;
|
||||
this.hardwareInfo = null;
|
||||
}
|
||||
|
||||
@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 "HttpUserHardware{" +
|
||||
"hardwareInfo=" + hardwareInfo +
|
||||
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
|
||||
", id=" + id +
|
||||
", banned=" + banned +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,14 @@
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MemoryAuthCoreProvider extends AuthCoreProvider {
|
||||
private transient final List<MemoryUser> memory = new ArrayList<>(16);
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
synchronized (memory) {
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
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.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 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, String accessToken, String serverID) throws IOException {
|
||||
return false; // Authorization not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
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() throws IOException {
|
||||
// Providers closed automatically
|
||||
}
|
||||
}
|
|
@ -1,59 +1,30 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
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.user.UserSupportHardware;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware {
|
||||
public MySQLSourceConfig mySQLHolder;
|
||||
|
||||
public int expireSeconds = 3600;
|
||||
public String uuidColumn;
|
||||
public String usernameColumn;
|
||||
public String accessTokenColumn;
|
||||
public String passwordColumn;
|
||||
public String serverIDColumn;
|
||||
public String hardwareIdColumn;
|
||||
public String table;
|
||||
|
||||
public String tableHWID = "hwids";
|
||||
public String tableHWIDLog = "hwidLog";
|
||||
public PasswordVerifier passwordVerifier;
|
||||
public double criticalCompareLevel = 1.0;
|
||||
public String customQueryByUUIDSQL;
|
||||
public String customQueryByUsernameSQL;
|
||||
public String customQueryByLoginSQL;
|
||||
public String customUpdateAuthSQL;
|
||||
public String customUpdateServerIdSQL;
|
||||
private transient String sqlFindHardwareByPublicKey;
|
||||
private transient String sqlFindHardwareByData;
|
||||
private transient String sqlFindHardwareById;
|
||||
|
@ -63,130 +34,16 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
|
|||
private transient String sqlUpdateHardwareBanned;
|
||||
private transient String sqlUpdateUsers;
|
||||
private transient String sqlUsersByHwidId;
|
||||
// Prepared SQL queries
|
||||
private transient String queryByUUIDSQL;
|
||||
private transient String queryByUsernameSQL;
|
||||
private transient String queryByLoginSQL;
|
||||
private transient String updateAuthSQL;
|
||||
private transient String updateServerIDSQL;
|
||||
|
||||
private transient LaunchServer server;
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
try {
|
||||
return query(queryByUsernameSQL, username);
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByUUID(UUID uuid) {
|
||||
try {
|
||||
return query(queryByUUIDSQL, uuid.toString());
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByLogin(String login) {
|
||||
try {
|
||||
return query(queryByLoginSQL, login);
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||
try {
|
||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
||||
var user = (MySQLUser) getUserByUUID(info.uuid());
|
||||
if(user == null) {
|
||||
return null;
|
||||
}
|
||||
return new MySQLUserSession(user);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new OAuthAccessTokenExpired();
|
||||
} catch (JwtException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||
String[] parts = refreshToken.split("\\.");
|
||||
if(parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String username = parts[0];
|
||||
String token = parts[1];
|
||||
var user = (MySQLUser) getUserByUsername(username);
|
||||
if(user == null || user.password == null) {
|
||||
return null;
|
||||
}
|
||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
||||
if(!token.equals(realToken)) {
|
||||
return null;
|
||||
}
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||
MySQLUser mySQLUser = (MySQLUser) getUserByLogin(login);
|
||||
if(mySQLUser == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if(context != null) {
|
||||
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
||||
if(plainPassword == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if(!passwordVerifier.check(mySQLUser.password, plainPassword.password)) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
}
|
||||
MySQLUserSession session = new MySQLUserSession(mySQLUser);
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
var refreshToken = mySQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt));
|
||||
if (minecraftAccess) {
|
||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||
updateAuth(mySQLUser, minecraftAccessToken);
|
||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
} else {
|
||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
}
|
||||
public SQLSourceConfig getSQLConfig() {
|
||||
return mySQLHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
this.server = server;
|
||||
if (mySQLHolder == null) logger.error("mySQLHolder cannot be null");
|
||||
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
||||
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
||||
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
|
||||
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
|
||||
if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null");
|
||||
if (table == null) logger.error("table cannot be null");
|
||||
// Prepare SQL queries
|
||||
String userInfoCols = String.format("%s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, hardwareIdColumn);
|
||||
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
|
||||
table, uuidColumn);
|
||||
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
|
||||
userInfoCols, table, usernameColumn);
|
||||
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
||||
|
||||
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
|
||||
table, accessTokenColumn, serverIDColumn, uuidColumn);
|
||||
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
|
||||
table, serverIDColumn, uuidColumn);
|
||||
super.init(server);
|
||||
String userInfoCols = makeUserCols();
|
||||
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
|
||||
if (sqlFindHardwareByPublicKey == null)
|
||||
sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID);
|
||||
|
@ -206,43 +63,15 @@ public void init(LaunchServer server) {
|
|||
sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
|
||||
}
|
||||
|
||||
protected boolean updateAuth(User user, String accessToken) throws IOException {
|
||||
try (Connection c = mySQLHolder.getConnection()) {
|
||||
MySQLUser mySQLUser = (MySQLUser) user;
|
||||
mySQLUser.accessToken = accessToken;
|
||||
PreparedStatement s = c.prepareStatement(updateAuthSQL);
|
||||
s.setString(1, accessToken);
|
||||
s.setString(2, user.getUUID().toString());
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
return s.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
@Override
|
||||
protected String makeUserCols() {
|
||||
return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
||||
try (Connection c = mySQLHolder.getConnection()) {
|
||||
MySQLUser mySQLUser = (MySQLUser) user;
|
||||
mySQLUser.serverId = serverID;
|
||||
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
|
||||
s.setString(1, serverID);
|
||||
s.setString(2, user.getUUID().toString());
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
return s.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mySQLHolder.close();
|
||||
}
|
||||
|
||||
private MySQLUser constructUser(ResultSet set) throws SQLException {
|
||||
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), new ClientPermissions(), set.getLong(hardwareIdColumn)) : null;
|
||||
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn)), set.getLong(hardwareIdColumn)) : null;
|
||||
}
|
||||
|
||||
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
|
||||
|
@ -271,19 +100,6 @@ private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) th
|
|||
s.executeUpdate();
|
||||
}
|
||||
|
||||
private User query(String sql, String value) throws IOException {
|
||||
try (Connection c = mySQLHolder.getConnection()) {
|
||||
PreparedStatement s = c.prepareStatement(sql);
|
||||
s.setString(1, value);
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
try (ResultSet set = s.executeQuery()) {
|
||||
return constructUser(set);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
|
||||
try (Connection connection = mySQLHolder.getConnection()) {
|
||||
|
@ -371,8 +187,8 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
|
|||
|
||||
@Override
|
||||
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
|
||||
MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession;
|
||||
MySQLUser mySQLUser = mySQLUserSession.user;
|
||||
SQLUserSession mySQLUserSession = (SQLUserSession) userSession;
|
||||
MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser();
|
||||
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
|
||||
if (mySQLUser.hwidId == mySQLUserHardware.id) return;
|
||||
mySQLUser.hwidId = mySQLUserHardware.id;
|
||||
|
@ -488,51 +304,15 @@ public String toString() {
|
|||
}
|
||||
}
|
||||
|
||||
public class MySQLUser implements User, UserSupportHardware {
|
||||
protected UUID uuid;
|
||||
protected String username;
|
||||
protected String accessToken;
|
||||
protected String serverId;
|
||||
protected String password;
|
||||
protected ClientPermissions permissions;
|
||||
public class MySQLUser extends SQLUser implements UserSupportHardware {
|
||||
protected long hwidId;
|
||||
protected transient MySQLUserHardware hardware;
|
||||
|
||||
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
this.accessToken = accessToken;
|
||||
this.serverId = serverId;
|
||||
this.password = password;
|
||||
this.permissions = permissions;
|
||||
super(uuid, username, accessToken, serverId, password, permissions);
|
||||
this.hwidId = hwidId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPermissions getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserHardware getHardware() {
|
||||
if (hardware != null) return hardware;
|
||||
|
@ -551,29 +331,4 @@ public String toString() {
|
|||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class MySQLUserSession implements UserSession {
|
||||
private final MySQLUser user;
|
||||
private final String id;
|
||||
|
||||
public MySQLUserSession(MySQLUser user) {
|
||||
this.user = user;
|
||||
this.id = user.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpireIn() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,297 +1,13 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
import pro.gravit.launchserver.auth.SQLSourceConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresSQLCoreProvider extends AuthCoreProvider {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
|
||||
public PostgreSQLSourceConfig postgresSQLHolder;
|
||||
|
||||
public int expireSeconds = 3600;
|
||||
public String uuidColumn;
|
||||
public String usernameColumn;
|
||||
public String accessTokenColumn;
|
||||
public String passwordColumn;
|
||||
public String serverIDColumn;
|
||||
public String table;
|
||||
|
||||
public PasswordVerifier passwordVerifier;
|
||||
public String customQueryByUUIDSQL;
|
||||
public String customQueryByUsernameSQL;
|
||||
public String customQueryByLoginSQL;
|
||||
public String customUpdateAuthSQL;
|
||||
public String customUpdateServerIdSQL;
|
||||
// Prepared SQL queries
|
||||
private transient String queryByUUIDSQL;
|
||||
private transient String queryByUsernameSQL;
|
||||
private transient String queryByLoginSQL;
|
||||
private transient String updateAuthSQL;
|
||||
private transient String updateServerIDSQL;
|
||||
|
||||
private transient LaunchServer server;
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
try {
|
||||
return query(queryByUsernameSQL, username);
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByUUID(UUID uuid) {
|
||||
try {
|
||||
return query(queryByUUIDSQL, uuid.toString());
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByLogin(String login) {
|
||||
try {
|
||||
return query(queryByLoginSQL, login);
|
||||
} catch (IOException e) {
|
||||
logger.error("SQL error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||
try {
|
||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
||||
var user = (PostgresSQLUser) getUserByUUID(info.uuid());
|
||||
if(user == null) {
|
||||
return null;
|
||||
}
|
||||
return new PostgresSQLCoreProvider.MySQLUserSession(user);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new OAuthAccessTokenExpired();
|
||||
} catch (JwtException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||
String[] parts = refreshToken.split("\\.");
|
||||
if(parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String username = parts[0];
|
||||
String token = parts[1];
|
||||
var user = (PostgresSQLUser) getUserByUsername(username);
|
||||
if(user == null || user.password == null) {
|
||||
return null;
|
||||
}
|
||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
||||
if(!token.equals(realToken)) {
|
||||
return null;
|
||||
}
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new PostgresSQLCoreProvider.MySQLUserSession(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) getUserByLogin(login);
|
||||
if(postgresSQLUser == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if(context != null) {
|
||||
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
|
||||
if(plainPassword == null) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
if(!passwordVerifier.check(postgresSQLUser.password, plainPassword.password)) {
|
||||
throw AuthException.wrongPassword();
|
||||
}
|
||||
}
|
||||
MySQLUserSession session = new MySQLUserSession(postgresSQLUser);
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
var refreshToken = postgresSQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt));
|
||||
if (minecraftAccess) {
|
||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||
updateAuth(postgresSQLUser, minecraftAccessToken);
|
||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
} else {
|
||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
this.server = server;
|
||||
if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null");
|
||||
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
||||
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
||||
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
|
||||
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
|
||||
if (table == null) logger.error("table cannot be null");
|
||||
// Prepare SQL queries
|
||||
String userInfoCols = String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
|
||||
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
|
||||
table, uuidColumn);
|
||||
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
|
||||
userInfoCols, table, usernameColumn);
|
||||
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
|
||||
|
||||
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
|
||||
table, accessTokenColumn, serverIDColumn, uuidColumn);
|
||||
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
|
||||
table, serverIDColumn, uuidColumn);
|
||||
}
|
||||
|
||||
protected boolean updateAuth(User user, String accessToken) throws IOException {
|
||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
|
||||
postgresSQLUser.accessToken = accessToken;
|
||||
PreparedStatement s = c.prepareStatement(updateAuthSQL);
|
||||
s.setString(1, accessToken);
|
||||
s.setString(2, user.getUUID().toString());
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
return s.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean updateServerID(User user, String serverID) throws IOException {
|
||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
||||
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
|
||||
postgresSQLUser.serverId = serverID;
|
||||
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
|
||||
s.setString(1, serverID);
|
||||
s.setString(2, user.getUUID().toString());
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
return s.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
postgresSQLHolder.close();
|
||||
}
|
||||
|
||||
private PostgresSQLUser constructUser(ResultSet set) throws SQLException {
|
||||
return set.next() ? new PostgresSQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
|
||||
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions()) : null;
|
||||
}
|
||||
|
||||
private User query(String sql, String value) throws IOException {
|
||||
try (Connection c = postgresSQLHolder.getConnection()) {
|
||||
PreparedStatement s = c.prepareStatement(sql);
|
||||
s.setString(1, value);
|
||||
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
|
||||
try (ResultSet set = s.executeQuery()) {
|
||||
return constructUser(set);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PostgresSQLUser implements User {
|
||||
protected UUID uuid;
|
||||
protected String username;
|
||||
protected String accessToken;
|
||||
protected String serverId;
|
||||
protected String password;
|
||||
protected ClientPermissions permissions;
|
||||
|
||||
public PostgresSQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
this.accessToken = accessToken;
|
||||
this.serverId = serverId;
|
||||
this.password = password;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPermissions getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PostgresSQLUser{" +
|
||||
"uuid=" + uuid +
|
||||
", username='" + username + '\'' +
|
||||
", permissions=" + permissions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class MySQLUserSession implements UserSession {
|
||||
private final PostgresSQLUser user;
|
||||
private final String id;
|
||||
|
||||
public MySQLUserSession(PostgresSQLUser user) {
|
||||
this.user = user;
|
||||
this.id = user.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpireIn() {
|
||||
return 0;
|
||||
}
|
||||
public SQLSourceConfig getSQLConfig() {
|
||||
return postgresSQLHolder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package pro.gravit.launchserver.auth.core.interfaces.provider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AuthSupportRemoteClientAccess {
|
||||
String getClientApiUrl();
|
||||
|
||||
List<String> getClientApiFeatures();
|
||||
}
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
public interface UserSessionSupportKeys {
|
||||
ClientProfileKeys getClientProfileKeys();
|
||||
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt, long refreshedAfter) {
|
||||
|
||||
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt,
|
||||
long refreshedAfter) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -19,29 +19,6 @@ public class JsonPasswordVerifier extends PasswordVerifier {
|
|||
public String url;
|
||||
public String bearerToken;
|
||||
|
||||
@Override
|
||||
public boolean check(String encryptedPassword, String password) {
|
||||
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
|
||||
if (response != null) {
|
||||
return response.success;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class JsonPasswordRequest {
|
||||
public String encryptedPassword;
|
||||
public String password;
|
||||
|
||||
public JsonPasswordRequest(String encryptedPassword, String password) {
|
||||
this.encryptedPassword = encryptedPassword;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JsonPasswordResponse {
|
||||
public boolean success;
|
||||
}
|
||||
|
||||
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
|
||||
HttpRequest.BodyPublisher publisher;
|
||||
if (request != null) {
|
||||
|
@ -78,4 +55,27 @@ public static <T, R> R jsonRequest(T request, String url, String bearerToken, Cl
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public static void registerProviders() {
|
|||
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);
|
||||
providers.register("django", DjangoPasswordVerifier.class);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
|
||||
import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
|
||||
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
|
||||
import pro.gravit.launcher.request.secure.HardwareReportRequest;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
import java.util.UUID;
|
||||
|
||||
public class JsonTextureProvider extends TextureProvider {
|
||||
public String url;
|
||||
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {
|
||||
}.getType();
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {}.getType();
|
||||
public String url;
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
|
|
@ -33,17 +33,6 @@ public static void registerProviders() {
|
|||
|
||||
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
|
||||
|
||||
@Deprecated
|
||||
public static class SkinAndCloakTextures {
|
||||
public final Texture skin;
|
||||
public final Texture cloak;
|
||||
|
||||
public SkinAndCloakTextures(Texture skin, Texture cloak) {
|
||||
this.skin = skin;
|
||||
this.cloak = cloak;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
|
||||
|
||||
|
@ -92,4 +81,15 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
|
|||
|
||||
return map;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class SkinAndCloakTextures {
|
||||
public final Texture skin;
|
||||
public final Texture cloak;
|
||||
|
||||
public SkinAndCloakTextures(Texture skin, Texture cloak) {
|
||||
this.skin = skin;
|
||||
this.cloak = cloak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,7 +211,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
|||
try {
|
||||
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
||||
InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public final class JARLauncherBinary extends LauncherBinary {
|
||||
|
@ -19,6 +21,8 @@ public final class JARLauncherBinary extends LauncherBinary {
|
|||
public final List<Path> coreLibs;
|
||||
public final List<Path> addonLibs;
|
||||
|
||||
public final Map<String, Path> files;
|
||||
|
||||
public JARLauncherBinary(LaunchServer server) throws IOException {
|
||||
super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar");
|
||||
count = new AtomicLong(0);
|
||||
|
@ -27,6 +31,7 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
|
|||
buildDir = server.dir.resolve("build");
|
||||
coreLibs = new ArrayList<>();
|
||||
addonLibs = new ArrayList<>();
|
||||
files = new HashMap<>();
|
||||
if (!Files.isDirectory(buildDir)) {
|
||||
Files.deleteIfExists(buildDir);
|
||||
Files.createDirectory(buildDir);
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
@ -48,6 +51,11 @@ public Path process(Path inputFile) throws IOException {
|
|||
}
|
||||
attach(output, inputFile, srv.launcherBinary.coreLibs);
|
||||
attach(output, inputFile, jars);
|
||||
for(var entry : srv.launcherBinary.files.entrySet()) {
|
||||
ZipEntry newEntry = IOHelper.newZipEntry(entry.getKey());
|
||||
output.putNextEntry(newEntry);
|
||||
IOHelper.transfer(entry.getValue(), output);
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class PrepareBuildTask implements LauncherBuildTask {
|
||||
private final LaunchServer server;
|
||||
|
@ -33,8 +35,19 @@ public String getName() {
|
|||
public Path process(Path inputFile) throws IOException {
|
||||
server.launcherBinary.coreLibs.clear();
|
||||
server.launcherBinary.addonLibs.clear();
|
||||
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), true);
|
||||
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), true);
|
||||
server.launcherBinary.files.clear();
|
||||
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false);
|
||||
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
|
||||
try(Stream<Path> stream = Files.walk(server.launcherPack).filter((e) -> {
|
||||
try {
|
||||
return !Files.isDirectory(e) && !Files.isHidden(e);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
})) {
|
||||
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString(), (v) -> v));
|
||||
server.launcherBinary.files.putAll(map);
|
||||
}
|
||||
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
|
||||
tryUnpack();
|
||||
return result;
|
||||
|
|
|
@ -83,8 +83,7 @@ private Path setConfig() {
|
|||
jre.setMinVersion(server.config.launch4j.minVersion);
|
||||
if (server.config.launch4j.setMaxVersion)
|
||||
jre.setMaxVersion(server.config.launch4j.maxVersion);
|
||||
jre.setRuntimeBits(Jre.RUNTIME_BITS_64_AND_32);
|
||||
jre.setJdkPreference(Jre.JDK_PREFERENCE_PREFER_JRE);
|
||||
jre.setPath(System.getProperty("java.home"));
|
||||
config.setJre(jre);
|
||||
|
||||
// Prepare version info (product)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
public class DebugCommand extends Command {
|
||||
private transient Logger logger = LogManager.getLogger();
|
||||
|
||||
public DebugCommand(LaunchServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
import pro.gravit.launchserver.command.Command;
|
||||
import pro.gravit.utils.Downloader;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import proguard.OutputWriter;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -22,11 +20,9 @@
|
|||
import java.util.List;
|
||||
|
||||
public final class DownloadAssetCommand extends Command {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
|
||||
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
public DownloadAssetCommand(LaunchServer server) {
|
||||
super(server);
|
||||
|
@ -44,11 +40,11 @@ public String getUsageDescription() {
|
|||
|
||||
@Override
|
||||
public void invoke(String... args) throws Exception {
|
||||
verifyArgs(args, 2);
|
||||
verifyArgs(args, 1);
|
||||
//Version version = Version.byName(args[0]);
|
||||
String versionName = args[0];
|
||||
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : "assets");
|
||||
String type = args.length > 2 ? args[2] : "mojang";
|
||||
String dirName = IOHelper.verifyFileName(args[1]);
|
||||
Path assetDir = server.updatesDir.resolve(dirName);
|
||||
|
||||
// Create asset dir
|
||||
|
|
|
@ -41,18 +41,14 @@ public void invoke(String... args) throws IOException, CommandException {
|
|||
verifyArgs(args, 2);
|
||||
//Version version = Version.byName(args[0]);
|
||||
String versionName = args[0];
|
||||
String dirName = IOHelper.verifyFileName(args[1]);
|
||||
Path clientDir = server.updatesDir.resolve(args[1]);
|
||||
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
|
||||
Path clientDir = server.updatesDir.resolve(dirName);
|
||||
|
||||
boolean isMirrorClientDownload = false;
|
||||
if (args.length > 2) {
|
||||
isMirrorClientDownload = args[2].equals("mirror");
|
||||
}
|
||||
|
||||
// Create client dir
|
||||
logger.info("Creating client dir: '{}'", dirName);
|
||||
Files.createDirectory(clientDir);
|
||||
|
||||
// Download required client
|
||||
logger.info("Downloading client, it may take some time");
|
||||
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
|
||||
|
@ -60,7 +56,25 @@ public void invoke(String... args) throws IOException, CommandException {
|
|||
|
||||
// Create profile file
|
||||
logger.info("Creaing profile file: '{}'", dirName);
|
||||
ClientProfile client = null;
|
||||
ClientProfile clientProfile = null;
|
||||
if (isMirrorClientDownload) {
|
||||
try {
|
||||
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
|
||||
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
|
||||
clientProfile.setTitle(dirName);
|
||||
clientProfile.setDir(dirName);
|
||||
clientProfile.setUUID(UUID.randomUUID());
|
||||
if (clientProfile.getServers() != null) {
|
||||
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
|
||||
if (serverProfile != null) {
|
||||
serverProfile.name = dirName;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
|
||||
isMirrorClientDownload = false;
|
||||
}
|
||||
}
|
||||
if (!isMirrorClientDownload) {
|
||||
try {
|
||||
String internalVersion = versionName;
|
||||
|
@ -75,27 +89,14 @@ public void invoke(String... args) throws IOException, CommandException {
|
|||
for (MakeProfileHelper.MakeProfileOption option : options) {
|
||||
logger.debug("Detected option {}", option.getClass().getSimpleName());
|
||||
}
|
||||
client = MakeProfileHelper.makeProfile(version, dirName, options);
|
||||
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
|
||||
} catch (Throwable e) {
|
||||
isMirrorClientDownload = true;
|
||||
}
|
||||
}
|
||||
if (isMirrorClientDownload) {
|
||||
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
|
||||
client = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
|
||||
client.setTitle(dirName);
|
||||
client.setDir(dirName);
|
||||
client.setUUID(UUID.randomUUID());
|
||||
if (client.getServers() != null) {
|
||||
ClientProfile.ServerProfile serverProfile = client.getDefaultServerProfile();
|
||||
if (serverProfile != null) {
|
||||
serverProfile.name = dirName;
|
||||
}
|
||||
}
|
||||
}
|
||||
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
|
||||
dirName, "json"))) {
|
||||
Launcher.gsonManager.configGson.toJson(client, writer);
|
||||
Launcher.gsonManager.configGson.toJson(clientProfile, writer);
|
||||
}
|
||||
|
||||
// Finished
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
public class TokenCommand extends Command {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
public TokenCommand(LaunchServer server) {
|
||||
super(server);
|
||||
this.childCommands.put("info", new SubCommand("[token]", "print token info") {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
import pro.gravit.utils.helper.UnpackHelper;
|
||||
import proguard.Configuration;
|
||||
import proguard.ConfigurationParser;
|
||||
import proguard.ParseException;
|
||||
import proguard.ProGuard;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -45,7 +45,7 @@ public final class LaunchServerConfig {
|
|||
|
||||
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
|
||||
LaunchServerConfig newConfig = new LaunchServerConfig();
|
||||
newConfig.mirrors = new String[]{"https://mirror.gravit.pro/5.3.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
|
||||
newConfig.mirrors = new String[]{"https://mirror.gravitlauncher.com/5.3.x/", "https://gravit-launcher-mirror.storage.googleapis.com/"};
|
||||
newConfig.launch4j = new LaunchServerConfig.ExeConf();
|
||||
newConfig.launch4j.enabled = false;
|
||||
newConfig.launch4j.copyright = "© GravitLauncher Team";
|
||||
|
@ -169,8 +169,11 @@ public void verify() {
|
|||
if (!updateMirror) {
|
||||
for (int i = 0; i < mirrors.length; ++i) {
|
||||
if ("https://mirror.gravit.pro/5.2.x/".equals(mirrors[i])) {
|
||||
logger.warn("Replace mirror 'https://mirror.gravit.pro/5.2.x/' to 'https://mirror.gravit.pro/5.3.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'");
|
||||
mirrors[i] = "https://mirror.gravit.pro/5.3.x/";
|
||||
logger.warn("Replace mirror 'https://mirror.gravit.pro/5.2.x/' to 'https://mirror.gravitlauncher.com/5.3.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'");
|
||||
mirrors[i] = "https://mirror.gravitlauncher.com/5.3.x/";
|
||||
} else if ("https://mirror.gravit.pro/5.3.x/".equals(mirrors[i])) {
|
||||
logger.warn("Replace mirror 'https://mirror.gravit.pro/5.3.x/' to 'https://mirror.gravitlauncher.com/5.3.x/'. If you really need to use original url, use '-Dlaunchserver.config.disableUpdateMirror=true'");
|
||||
mirrors[i] = "https://mirror.gravitlauncher.com/5.3.x/";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,6 +284,7 @@ public static class NettyConfig {
|
|||
public boolean ipForwarding;
|
||||
public boolean disableWebApiInterface;
|
||||
public boolean showHiddenFiles;
|
||||
public boolean sendProfileUpdatesEvent = true;
|
||||
public String launcherURL;
|
||||
public String downloadURL;
|
||||
public String launcherEXEURL;
|
||||
|
|
|
@ -23,71 +23,11 @@
|
|||
|
||||
public final class HttpHelper {
|
||||
private static transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
private HttpHelper() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static class HttpOptional<T,E> {
|
||||
protected final T result;
|
||||
protected final E error;
|
||||
protected final int statusCode;
|
||||
|
||||
public HttpOptional(T result, E error, int statusCode) {
|
||||
this.result = result;
|
||||
this.error = error;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public T result() {
|
||||
return result;
|
||||
}
|
||||
public E error() {
|
||||
return error;
|
||||
}
|
||||
public int statusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
public boolean isSuccessful() {
|
||||
return statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
public T getOrThrow() throws RequestException {
|
||||
if(isSuccessful()) {
|
||||
return result;
|
||||
} else {
|
||||
throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface HttpErrorHandler<T, E> {
|
||||
HttpOptional<T,E> apply(HttpResponse<InputStream> response);
|
||||
}
|
||||
|
||||
public interface HttpJsonErrorHandler<T, E> extends HttpErrorHandler<T,E> {
|
||||
HttpOptional<T,E> applyJson(JsonElement response, int statusCode);
|
||||
default HttpOptional<T,E> apply(HttpResponse<InputStream> response) {
|
||||
try(Reader reader = new InputStreamReader(response.body())) {
|
||||
var element = Launcher.gsonManager.gson.fromJson(reader, JsonElement.class);
|
||||
return applyJson(element, response.statusCode());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> {
|
||||
private final Class<T> type;
|
||||
|
||||
public BasicJsonHttpErrorHandler(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpOptional<T, Void> applyJson(JsonElement response, int statusCode) {
|
||||
return new HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, E> HttpOptional<T, E> send(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException {
|
||||
try {
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
|
@ -97,7 +37,6 @@ public static<T,E> HttpOptional<T,E> send(HttpClient client, HttpRequest request
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T, E> CompletableFuture<HttpOptional<T, E>> sendAsync(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException {
|
||||
return client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply(handler::apply);
|
||||
}
|
||||
|
@ -120,6 +59,73 @@ public static<T> HttpRequest.BodyPublisher jsonBodyPublisher(T obj) {
|
|||
return HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(obj));
|
||||
}
|
||||
|
||||
|
||||
public interface HttpErrorHandler<T, E> {
|
||||
HttpOptional<T, E> apply(HttpResponse<InputStream> response);
|
||||
}
|
||||
|
||||
public interface HttpJsonErrorHandler<T, E> extends HttpErrorHandler<T, E> {
|
||||
HttpOptional<T, E> applyJson(JsonElement response, int statusCode);
|
||||
|
||||
default HttpOptional<T, E> apply(HttpResponse<InputStream> response) {
|
||||
try (Reader reader = new InputStreamReader(response.body())) {
|
||||
var element = Launcher.gsonManager.gson.fromJson(reader, JsonElement.class);
|
||||
return applyJson(element, response.statusCode());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpOptional<T, E> {
|
||||
protected final T result;
|
||||
protected final E error;
|
||||
protected final int statusCode;
|
||||
|
||||
public HttpOptional(T result, E error, int statusCode) {
|
||||
this.result = result;
|
||||
this.error = error;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public T result() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public E error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public int statusCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
|
||||
public T getOrThrow() throws RequestException {
|
||||
if (isSuccessful()) {
|
||||
return result;
|
||||
} else {
|
||||
throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> {
|
||||
private final Class<T> type;
|
||||
|
||||
public BasicJsonHttpErrorHandler(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpOptional<T, Void> applyJson(JsonElement response, int statusCode) {
|
||||
return new HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
private static class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T> {
|
||||
private final HttpResponse.BodyHandler<InputStream> delegate;
|
||||
private final Function<InputStream, T> func;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
|
|
|
@ -150,6 +150,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
|||
} else {
|
||||
try (Reader reader = IOHelper.newReader(configPath)) {
|
||||
targetConfig = Launcher.gsonManager.configGson.fromJson(reader, clazz);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error when reading config {} in module {}: {}", configPath, file, e);
|
||||
return super.visitFile(file, attrs);
|
||||
}
|
||||
}
|
||||
if (entity.propertyMap == null) entity.propertyMap = new HashMap<>();
|
||||
|
|
|
@ -27,13 +27,7 @@
|
|||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
|
||||
public class AuthManager {
|
||||
|
@ -60,9 +54,6 @@ public String newCheckServerToken(String serverName, String authId) {
|
|||
.compact();
|
||||
}
|
||||
|
||||
public record CheckServerTokenInfo(String serverName, String authId) {
|
||||
}
|
||||
|
||||
public CheckServerTokenInfo parseCheckServerToken(String token) {
|
||||
try {
|
||||
var jwt = checkServerTokenParser.parseClaimsJws(token).getBody();
|
||||
|
@ -72,29 +63,6 @@ public CheckServerTokenInfo parseCheckServerToken(String token) {
|
|||
}
|
||||
}
|
||||
|
||||
public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider {
|
||||
private final LaunchServer server;
|
||||
|
||||
public CheckServerVerifier(LaunchServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
|
||||
var info = server.authManager.parseCheckServerToken(extendedToken);
|
||||
if(info == null) {
|
||||
return false;
|
||||
}
|
||||
client.auth_id = info.authId;
|
||||
client.auth = server.config.getAuthProviderPair(info.authId);
|
||||
if(client.permissions == null) client.permissions = new ClientPermissions();
|
||||
client.permissions.addPerm("launchserver.checkserver");
|
||||
client.permissions.addPerm(String.format("launchserver.profile.%s.show", info.serverName));
|
||||
client.setProperty("launchserver.serverName", info.serverName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AuthContext
|
||||
*
|
||||
|
@ -325,6 +293,32 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au
|
|||
return password;
|
||||
}
|
||||
|
||||
public record CheckServerTokenInfo(String serverName, String authId) {
|
||||
}
|
||||
|
||||
public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider {
|
||||
private final LaunchServer server;
|
||||
|
||||
public CheckServerVerifier(LaunchServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
|
||||
var info = server.authManager.parseCheckServerToken(extendedToken);
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
client.auth_id = info.authId;
|
||||
client.auth = server.config.getAuthProviderPair(info.authId);
|
||||
if (client.permissions == null) client.permissions = new ClientPermissions();
|
||||
client.permissions.addPerm("launchserver.checkserver");
|
||||
client.permissions.addPerm(String.format("launchserver.profile.%s.show", info.serverName));
|
||||
client.setProperty("launchserver.serverName", info.serverName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CheckServerReport {
|
||||
public UUID uuid;
|
||||
public User user;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package pro.gravit.launchserver.manangers.hook;
|
||||
|
||||
import pro.gravit.launchserver.auth.core.User;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
|
|
|
@ -93,6 +93,22 @@ public static void registerResponses() {
|
|||
providers.register("getPublicKey", GetPublicKeyResponse.class);
|
||||
}
|
||||
|
||||
public static String getIPFromContext(ChannelHandlerContext ctx) {
|
||||
var handler = ctx.pipeline().get(WebSocketFrameHandler.class);
|
||||
if (handler == null || handler.context == null || handler.context.ip == null) {
|
||||
return IOHelper.getIP(ctx.channel().remoteAddress());
|
||||
}
|
||||
return handler.context.ip;
|
||||
}
|
||||
|
||||
public static String getIPFromChannel(Channel channel) {
|
||||
var handler = channel.pipeline().get(WebSocketFrameHandler.class);
|
||||
if (handler == null || handler.context == null || handler.context.ip == null) {
|
||||
return IOHelper.getIP(channel.remoteAddress());
|
||||
}
|
||||
return handler.context.ip;
|
||||
}
|
||||
|
||||
public void forEachActiveChannels(BiConsumer<Channel, WebSocketFrameHandler> callback) {
|
||||
for (Channel channel : channels) {
|
||||
if (channel == null || channel.pipeline() == null) continue;
|
||||
|
@ -176,22 +192,6 @@ public void registerClient(Channel channel) {
|
|||
channels.add(channel);
|
||||
}
|
||||
|
||||
public static String getIPFromContext(ChannelHandlerContext ctx) {
|
||||
var handler = ctx.pipeline().get(WebSocketFrameHandler.class);
|
||||
if(handler == null || handler.context == null || handler.context.ip == null) {
|
||||
return IOHelper.getIP(ctx.channel().remoteAddress());
|
||||
}
|
||||
return handler.context.ip;
|
||||
}
|
||||
|
||||
public static String getIPFromChannel(Channel channel) {
|
||||
var handler = channel.pipeline().get(WebSocketFrameHandler.class);
|
||||
if(handler == null || handler.context == null || handler.context.ip == null) {
|
||||
return IOHelper.getIP(channel.remoteAddress());
|
||||
}
|
||||
return handler.context.ip;
|
||||
}
|
||||
|
||||
public void sendObject(ChannelHandlerContext ctx, Object obj) {
|
||||
String msg = gson.toJson(obj, WebSocketEvent.class);
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package pro.gravit.launchserver.socket.response.auth;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.events.request.AdditionalDataRequestEvent;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.User;
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
import pro.gravit.utils.HookException;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AuthResponse extends SimpleResponse {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public String login;
|
||||
|
@ -46,6 +44,7 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
|||
server.authHookManager.preHook.hook(context, clientData);
|
||||
context.report = server.authManager.auth(context, password);
|
||||
server.authHookManager.postHook.hook(context, clientData);
|
||||
result.permissions = context.report.session() != null ? (context.report.session().getUser() != null ? context.report.session().getUser().getPermissions() : null) : null;
|
||||
if (context.report.isUsingOAuth()) {
|
||||
result.oauth = new AuthRequestEvent.OAuthRequestEvent(context.report.oauthAccessToken(), context.report.oauthRefreshToken(), context.report.oauthExpire());
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.events.request.FetchClientProfileKeyRequestEvent;
|
||||
import pro.gravit.launchserver.auth.core.User;
|
||||
import pro.gravit.launchserver.auth.core.UserSession;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRemoteClientAccess;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
|
||||
|
@ -19,8 +20,14 @@ public String getType() {
|
|||
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||
List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>();
|
||||
for (AuthProviderPair pair : server.config.auth.values()) {
|
||||
var rca = pair.isSupport(AuthSupportRemoteClientAccess.class);
|
||||
if (rca != null) {
|
||||
list.add(new GetAvailabilityAuthRequestEvent.AuthAvailability(pair.name, pair.displayName,
|
||||
pair.core.getDetails(client)));
|
||||
pair.visible, pair.core.getDetails(client), rca.getClientApiUrl(), rca.getClientApiFeatures()));
|
||||
} else {
|
||||
list.add(new GetAvailabilityAuthRequestEvent.AuthAvailability(pair.name, pair.displayName,
|
||||
pair.visible, pair.core.getDetails(client)));
|
||||
}
|
||||
}
|
||||
sendResult(new GetAvailabilityAuthRequestEvent(list));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import io.netty.channel.ChannelHandlerContext;
|
||||
import pro.gravit.launcher.events.request.ProfilesRequestEvent;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
|
||||
import pro.gravit.launchserver.socket.Client;
|
||||
import pro.gravit.launchserver.socket.response.SimpleResponse;
|
||||
|
@ -12,18 +13,7 @@
|
|||
import java.util.Set;
|
||||
|
||||
public class ProfilesResponse extends SimpleResponse {
|
||||
@Override
|
||||
public String getType() {
|
||||
return "profiles";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler && !((ProfilesProtectHandler) server.config.protectHandler).canGetProfiles(client)) {
|
||||
sendError("Access denied");
|
||||
return;
|
||||
}
|
||||
|
||||
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
|
||||
List<ClientProfile> profileList;
|
||||
Set<ClientProfile> serverProfiles = server.getProfiles();
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
|
||||
|
@ -36,6 +26,20 @@ public void execute(ChannelHandlerContext ctx, Client client) {
|
|||
} else {
|
||||
profileList = List.copyOf(serverProfiles);
|
||||
}
|
||||
sendResult(new ProfilesRequestEvent(profileList));
|
||||
return profileList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "profiles";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ChannelHandlerContext ctx, Client client) {
|
||||
if (server.config.protectHandler instanceof ProfilesProtectHandler && !((ProfilesProtectHandler) server.config.protectHandler).canGetProfiles(client)) {
|
||||
sendError("Access denied");
|
||||
return;
|
||||
}
|
||||
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
|
||||
}
|
||||
}
|
||||
|
|
4
LaunchServer/src/main/resources/experimental-build.json
Normal file
4
LaunchServer/src/main/resources/experimental-build.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"features": [],
|
||||
"info": []
|
||||
}
|
|
@ -81,7 +81,7 @@ task dumpLibs(type: Copy) {
|
|||
pom {
|
||||
name = 'GravitLauncher Client API'
|
||||
description = 'GravitLauncher Client Module API'
|
||||
url = 'https://launcher.gravit.pro'
|
||||
url = 'https://gravitlauncher.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU General Public License, Version 3.0'
|
||||
|
@ -103,7 +103,7 @@ task dumpLibs(type: Copy) {
|
|||
scm {
|
||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||
url = 'https://launcher.gravit.pro/'
|
||||
url = 'https://gravitlauncher.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.RequestService;
|
||||
import pro.gravit.launcher.request.WebSocketEvent;
|
||||
import pro.gravit.launcher.request.websockets.ClientWebSocketService;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
public class BasicLauncherEventHandler implements RequestService.EventHandler {
|
||||
|
|
|
@ -179,7 +179,7 @@ public static void applyBasicOfflineProcessors(OfflineRequestService service) {
|
|||
service.registerRequestProcessor(GetAvailabilityAuthRequest.class, (r) -> {
|
||||
List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details = new ArrayList<>();
|
||||
details.add(new AuthLoginOnlyDetails());
|
||||
GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability = new GetAvailabilityAuthRequestEvent.AuthAvailability("offline", "Offline Mode", details);
|
||||
GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability = new GetAvailabilityAuthRequestEvent.AuthAvailability("offline", "Offline Mode", true, details);
|
||||
List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>(1);
|
||||
list.add(authAvailability);
|
||||
return new GetAvailabilityAuthRequestEvent(list);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package pro.gravit.launcher.api;
|
||||
|
||||
import pro.gravit.launcher.LauncherEngine;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
public class SystemService {
|
||||
private SystemService() {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launcher.LauncherConfig;
|
||||
import pro.gravit.launcher.LauncherEngine;
|
||||
import pro.gravit.launcher.LauncherNetworkAPI;
|
||||
import pro.gravit.launcher.client.events.client.ClientProcessBuilderCreateEvent;
|
||||
import pro.gravit.launcher.client.events.client.ClientProcessBuilderLaunchedEvent;
|
||||
import pro.gravit.launcher.client.events.client.ClientProcessBuilderParamsWrittedEvent;
|
||||
|
@ -28,7 +27,6 @@
|
|||
import java.net.SocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package pro.gravit.launcher.client;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
|
@ -162,9 +163,13 @@ private Result modernPing(HInput input, HOutput output) throws IOException {
|
|||
}
|
||||
|
||||
// Parse JSON response
|
||||
JsonObject object = JsonParser.parseString(response).getAsJsonObject();
|
||||
JsonElement element = JsonParser.parseString(response);
|
||||
if(element.isJsonPrimitive()) {
|
||||
throw new IOException(element.getAsString());
|
||||
}
|
||||
JsonObject object = element.getAsJsonObject();
|
||||
if (object.has("error")) {
|
||||
throw new IOException(object.get("error").getAsString());
|
||||
throw new IOException(object.get("error").getAsString()); // May be not needed?
|
||||
}
|
||||
JsonObject playersObject = object.get("players").getAsJsonObject();
|
||||
int online = playersObject.get("online").getAsInt();
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package pro.gravit.launcher.utils;
|
||||
|
||||
import pro.gravit.launcher.AsyncDownloader;
|
||||
import pro.gravit.launcher.Launcher;
|
||||
import pro.gravit.launcher.LauncherEngine;
|
||||
import pro.gravit.launcher.LauncherInject;
|
||||
import pro.gravit.launcher.events.request.LauncherRequestEvent;
|
||||
import pro.gravit.launcher.request.update.LauncherRequest;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
@ -13,12 +11,10 @@
|
|||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -30,9 +26,11 @@
|
|||
public class LauncherUpdater {
|
||||
@LauncherInject("launcher.certificatePinning")
|
||||
private static boolean isCertificatePinning;
|
||||
|
||||
public static void nothing() {
|
||||
|
||||
}
|
||||
|
||||
private static Path getLauncherPath() {
|
||||
Path pathToCore = IOHelper.getCodeSource(IOHelper.class);
|
||||
Path pathToApi = IOHelper.getCodeSource(LauncherRequest.class);
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package pro.gravit.launcher.utils;
|
||||
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
public final class NativeJVMHalt {
|
||||
public final int haltCode;
|
||||
|
|
|
@ -59,7 +59,7 @@ task javadocJar(type: Jar) {
|
|||
pom {
|
||||
name = 'GravitLauncher WebSocket API'
|
||||
description = 'GravitLauncher WebSocket Module API'
|
||||
url = 'https://launcher.gravit.pro'
|
||||
url = 'https://gravitlauncher.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU General Public License, Version 3.0'
|
||||
|
@ -81,7 +81,7 @@ task javadocJar(type: Jar) {
|
|||
scm {
|
||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||
url = 'https://launcher.gravit.pro/'
|
||||
url = 'https://gravitlauncher.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package pro.gravit.launcher;
|
||||
|
||||
import pro.gravit.launcher.serialize.HInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class ClientPermissions {
|
||||
|
|
|
@ -46,10 +46,27 @@ public static class AuthAvailability {
|
|||
@LauncherNetworkAPI
|
||||
public String displayName;
|
||||
|
||||
public AuthAvailability(String name, String displayName, List<AuthAvailabilityDetails> details) {
|
||||
@LauncherNetworkAPI
|
||||
public boolean visible;
|
||||
@LauncherNetworkAPI
|
||||
public String apiUrl;
|
||||
@LauncherNetworkAPI
|
||||
public List<String> apiFeatures;
|
||||
|
||||
public AuthAvailability(String name, String displayName, boolean visible, List<AuthAvailabilityDetails> details) {
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.visible = visible;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public AuthAvailability(String name, String displayName, boolean visible, List<AuthAvailabilityDetails> details, String apiUrl, List<String> apiFeatures) {
|
||||
this.visible = visible;
|
||||
this.details = details;
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.apiUrl = apiUrl;
|
||||
this.apiFeatures = apiFeatures;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -484,7 +484,8 @@ public enum Version {
|
|||
MC1182("1.18.2", 758),
|
||||
MC119("1.19", 759),
|
||||
MC1191("1.19.1", 760),
|
||||
MC1192("1.19.2", 760);
|
||||
MC1192("1.19.2", 760),
|
||||
MC1193("1.19.3", 761);
|
||||
private static final Map<String, Version> VERSIONS;
|
||||
|
||||
static {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package pro.gravit.launcher.profiles;
|
||||
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.VerifyHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
|
@ -27,6 +27,21 @@ public OptionalView(OptionalView view) {
|
|||
this.all = view.all;
|
||||
}
|
||||
|
||||
public OptionalView(ClientProfile profile, OptionalView old) {
|
||||
this(profile);
|
||||
for(OptionalFile oldFile : old.all) {
|
||||
OptionalFile newFile = findByName(oldFile.name);
|
||||
if(newFile == null) {
|
||||
continue;
|
||||
}
|
||||
if(old.isEnabled(oldFile)) {
|
||||
enable(newFile, old.installInfo.get(oldFile).isManual, (file, status) -> {});
|
||||
} else {
|
||||
disable(newFile, (file, status) -> {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends OptionalAction> Set<T> getActionsByClass(Class<T> clazz) {
|
||||
Set<T> results = new HashSet<>();
|
||||
|
@ -42,6 +57,19 @@ public <T extends OptionalAction> Set<T> getActionsByClass(Class<T> clazz) {
|
|||
return results;
|
||||
}
|
||||
|
||||
public OptionalFile findByName(String name) {
|
||||
for(OptionalFile file : all) {
|
||||
if(name.equals(file.name)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEnabled(OptionalFile file) {
|
||||
return enabled.contains(file);
|
||||
}
|
||||
|
||||
public Set<OptionalAction> getEnabledActions() {
|
||||
Set<OptionalAction> results = new HashSet<>();
|
||||
for (OptionalFile e : enabled) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
public class ArchTrigger extends OptionalTrigger {
|
||||
public JVMHelper.ARCH arch;
|
||||
|
||||
@Override
|
||||
protected boolean isTriggered(OptionalFile optional, OptionalTriggerContext context) {
|
||||
return context.getJavaVersion().arch == arch;
|
||||
|
|
|
@ -28,6 +28,10 @@ public abstract class Request<R extends WebSocketEvent> implements WebSocketRequ
|
|||
public final UUID requestUUID = UUID.randomUUID();
|
||||
private transient final AtomicBoolean started = new AtomicBoolean(false);
|
||||
|
||||
public static RequestService getRequestService() {
|
||||
return requestService;
|
||||
}
|
||||
|
||||
public static void setRequestService(RequestService service) {
|
||||
requestService = service;
|
||||
if (service instanceof StdWebSocketService) {
|
||||
|
@ -35,10 +39,6 @@ public static void setRequestService(RequestService service) {
|
|||
}
|
||||
}
|
||||
|
||||
public static RequestService getRequestService() {
|
||||
return requestService;
|
||||
}
|
||||
|
||||
public static boolean isAvailable() {
|
||||
return requestService != null;
|
||||
}
|
||||
|
@ -121,18 +121,6 @@ public static RequestRestoreReport reconnect() throws Exception {
|
|||
return restore();
|
||||
}
|
||||
|
||||
public static class RequestRestoreReport {
|
||||
public final boolean legacySession;
|
||||
public final boolean refreshed;
|
||||
public final List<String> invalidExtendedTokens;
|
||||
|
||||
public RequestRestoreReport(boolean legacySession, boolean refreshed, List<String> invalidExtendedTokens) {
|
||||
this.legacySession = legacySession;
|
||||
this.refreshed = refreshed;
|
||||
this.invalidExtendedTokens = invalidExtendedTokens;
|
||||
}
|
||||
}
|
||||
|
||||
public static RequestRestoreReport restore() throws Exception {
|
||||
boolean refreshed = false;
|
||||
RestoreRequest request;
|
||||
|
@ -224,4 +212,16 @@ public interface ExtendedTokenCallback {
|
|||
String tryGetNewToken(String name);
|
||||
}
|
||||
|
||||
public static class RequestRestoreReport {
|
||||
public final boolean legacySession;
|
||||
public final boolean refreshed;
|
||||
public final List<String> invalidExtendedTokens;
|
||||
|
||||
public RequestRestoreReport(boolean legacySession, boolean refreshed, List<String> invalidExtendedTokens) {
|
||||
this.legacySession = legacySession;
|
||||
this.refreshed = refreshed;
|
||||
this.invalidExtendedTokens = invalidExtendedTokens;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
public interface RequestService {
|
||||
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException;
|
||||
|
||||
void registerEventHandler(EventHandler handler);
|
||||
|
||||
void unregisterEventHandler(EventHandler handler);
|
||||
|
||||
default <T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException {
|
||||
try {
|
||||
return request(request).get();
|
||||
|
|
|
@ -5,21 +5,13 @@
|
|||
import pro.gravit.launcher.events.request.LauncherRequestEvent;
|
||||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.RequestService;
|
||||
import pro.gravit.launcher.request.websockets.StdWebSocketService;
|
||||
import pro.gravit.launcher.request.websockets.WebSocketRequest;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class LauncherRequest extends Request<LauncherRequestEvent> implements WebSocketRequest {
|
||||
public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.websockets.WebSocketRequest;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.VerifyHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import pro.gravit.launcher.events.request.ProfileByUsernameRequestEvent;
|
||||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.websockets.WebSocketRequest;
|
||||
import pro.gravit.utils.helper.VerifyHelper;
|
||||
|
||||
public final class ProfileByUsernameRequest extends Request<ProfileByUsernameRequestEvent> implements WebSocketRequest {
|
||||
@LauncherNetworkAPI
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
public class OfflineRequestService implements RequestService {
|
||||
private final HashSet<EventHandler> eventHandlers = new HashSet<>();
|
||||
private final Map<Class<?>, RequestProcessor<?, ?>> processors = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
|
||||
|
|
|
@ -55,7 +55,6 @@ public static CompletableFuture<StdWebSocketService> initWebSockets(String addre
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Deprecated
|
||||
public void registerEventHandler(ClientWebSocketService.EventHandler handler) {
|
||||
legacyEventHandlers.add(handler);
|
||||
|
|
|
@ -65,7 +65,7 @@ task javadocJar(type: Jar) {
|
|||
pom {
|
||||
name = 'GravitLauncher Core Utils'
|
||||
description = 'GravitLauncher Core Utils'
|
||||
url = 'https://launcher.gravit.pro'
|
||||
url = 'https://gravitlauncher.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU General Public License, Version 3.0'
|
||||
|
@ -87,7 +87,7 @@ task javadocJar(type: Jar) {
|
|||
scm {
|
||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||
url = 'https://launcher.gravit.pro/'
|
||||
url = 'https://gravitlauncher.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,10 @@ public long size() {
|
|||
return size;
|
||||
}
|
||||
|
||||
public byte[] getDigest() {
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(HOutput output) throws IOException {
|
||||
output.writeVarLong(size);
|
||||
|
|
|
@ -56,6 +56,7 @@ public static void downloadFile(URL url, Path file, Consumer<Integer> chanheTrac
|
|||
|
||||
public static void downloadZip(URL url, Path dir) throws IOException {
|
||||
try (ZipInputStream input = IOHelper.newZipInput(url)) {
|
||||
Files.createDirectory(dir);
|
||||
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
|
||||
if (entry.isDirectory()) {
|
||||
Files.createDirectory(dir.resolve(IOHelper.toPath(entry.getName())));
|
||||
|
|
|
@ -6,7 +6,7 @@ public final class Version implements Comparable<Version> {
|
|||
|
||||
public static final int MAJOR = 5;
|
||||
public static final int MINOR = 3;
|
||||
public static final int PATCH = 0;
|
||||
public static final int PATCH = 6;
|
||||
public static final int BUILD = 1;
|
||||
public static final Version.Type RELEASE = Type.STABLE;
|
||||
public final int major;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import pro.gravit.utils.command.CommandException;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
|
|
|
@ -39,6 +39,7 @@ public final class IOHelper {
|
|||
public static final FileSystem FS = FileSystems.getDefault();
|
||||
// Platform-dependent
|
||||
public static final String PLATFORM_SEPARATOR = FS.getSeparator();
|
||||
private static final Pattern PLATFORM_SEPARATOR_PATTERN = Pattern.compile(PLATFORM_SEPARATOR, Pattern.LITERAL);
|
||||
public static final boolean POSIX = FS.supportedFileAttributeViews().contains("posix") || FS.supportedFileAttributeViews().contains("Posix");
|
||||
public static final Path JVM_DIR = Paths.get(System.getProperty("java.home"));
|
||||
public static final Path HOME_DIR = Paths.get(System.getProperty("user.home"));
|
||||
|
@ -54,7 +55,6 @@ public final class IOHelper {
|
|||
private static final Set<FileVisitOption> WALK_OPTIONS = Collections.singleton(FileVisitOption.FOLLOW_LINKS);
|
||||
// Other constants
|
||||
private static final Pattern CROSS_SEPARATOR_PATTERN = Pattern.compile(CROSS_SEPARATOR, Pattern.LITERAL);
|
||||
private static final Pattern PLATFORM_SEPARATOR_PATTERN = Pattern.compile(PLATFORM_SEPARATOR, Pattern.LITERAL);
|
||||
|
||||
private IOHelper() {
|
||||
}
|
||||
|
|
|
@ -20,12 +20,10 @@ public final class JVMHelper {
|
|||
public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN =
|
||||
ManagementFactory.getOperatingSystemMXBean();
|
||||
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName());
|
||||
// System properties
|
||||
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
|
||||
|
||||
@Deprecated
|
||||
public static final int OS_BITS = getCorrectOSArch();
|
||||
|
||||
// System properties
|
||||
public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion();
|
||||
public static final ARCH ARCH_TYPE = getArch(System.getProperty("os.arch"));
|
||||
public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
|
||||
public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
|
||||
|
@ -46,16 +44,6 @@ public final class JVMHelper {
|
|||
private JVMHelper() {
|
||||
}
|
||||
|
||||
public enum ARCH {
|
||||
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
|
||||
|
||||
public final String name;
|
||||
|
||||
ARCH(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static ARCH getArch(String arch) {
|
||||
if (arch.equals("amd64") || arch.equals("x86-64") || arch.equals("x86_64")) return ARCH.X86_64;
|
||||
if (arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
|
||||
|
@ -110,19 +98,16 @@ public static Class<?> firstClass(String... names) throws ClassNotFoundException
|
|||
throw new ClassNotFoundException(Arrays.toString(names));
|
||||
}
|
||||
|
||||
|
||||
public static void fullGC() {
|
||||
RUNTIME.gc();
|
||||
RUNTIME.runFinalization();
|
||||
LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20);
|
||||
}
|
||||
|
||||
|
||||
public static String[] getClassPath() {
|
||||
return System.getProperty("java.class.path").split(File.pathSeparator);
|
||||
}
|
||||
|
||||
|
||||
public static URL[] getClassPathURL() {
|
||||
String[] cp = System.getProperty("java.class.path").split(File.pathSeparator);
|
||||
URL[] list = new URL[cp.length];
|
||||
|
@ -164,35 +149,29 @@ private static int getCorrectOSArch() {
|
|||
return System.getProperty("os.arch").contains("64") ? 64 : 32;
|
||||
}
|
||||
|
||||
|
||||
public static String getEnvPropertyCaseSensitive(String name) {
|
||||
return System.getenv().get(name);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public static boolean isJVMMatchesSystemArch() {
|
||||
return JVM_BITS == OS_BITS;
|
||||
}
|
||||
|
||||
|
||||
public static String jvmProperty(String name, String value) {
|
||||
return String.format("-D%s=%s", name, value);
|
||||
}
|
||||
|
||||
|
||||
public static String systemToJvmProperty(String name) {
|
||||
return String.format("-D%s=%s", name, System.getProperties().getProperty(name));
|
||||
}
|
||||
|
||||
|
||||
public static void addSystemPropertyToArgs(Collection<String> args, String name) {
|
||||
String property = System.getProperty(name);
|
||||
if (property != null)
|
||||
args.add(String.format("-D%s=%s", name, property));
|
||||
}
|
||||
|
||||
|
||||
public static void verifySystemProperties(Class<?> mainClass, boolean requireSystem) {
|
||||
Locale.setDefault(Locale.US);
|
||||
// Verify class loader
|
||||
|
@ -204,6 +183,17 @@ public static void verifySystemProperties(Class<?> mainClass, boolean requireSys
|
|||
LogHelper.debug("Verifying JVM architecture");
|
||||
}
|
||||
|
||||
|
||||
public enum ARCH {
|
||||
X86("x86"), X86_64("x86-64"), ARM64("arm64"), ARM32("arm32");
|
||||
|
||||
public final String name;
|
||||
|
||||
ARCH(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum OS {
|
||||
MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx");
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
public class JavaHelper {
|
||||
private static List<JavaVersion> javaVersionsCache;
|
||||
|
||||
public static Path tryGetOpenJFXPath(Path jvmDir) {
|
||||
String dirName = jvmDir.getFileName().toString();
|
||||
Path parent = jvmDir.getParent();
|
||||
|
@ -249,7 +250,7 @@ public static JavaVersion getByPath(Path jvmDir) throws IOException {
|
|||
arch = null;
|
||||
}
|
||||
} else {
|
||||
versionAndBuild = new JavaVersionAndBuild(isExistExtJavaLibrary(jvmDir, "jfxrt") ? 8 : 9, 0);
|
||||
versionAndBuild = new JavaVersionAndBuild(isExistExtJavaLibrary(jvmDir, "rt") ? 8 : 9, 0);
|
||||
}
|
||||
JavaVersion resultJavaVersion = new JavaVersion(jvmDir, versionAndBuild.version, versionAndBuild.build, arch, false);
|
||||
if (versionAndBuild.version <= 8) {
|
||||
|
@ -264,8 +265,10 @@ public static JavaVersion getByPath(Path jvmDir) throws IOException {
|
|||
|
||||
public static boolean isExistExtJavaLibrary(Path jvmDir, String name) {
|
||||
Path jrePath = jvmDir.resolve("lib").resolve("ext").resolve(name.concat(".jar"));
|
||||
Path jrePathLin = jvmDir.resolve("lib").resolve(name.concat(".jar"));
|
||||
Path jdkPath = jvmDir.resolve("jre").resolve("lib").resolve("ext").resolve(name.concat(".jar"));
|
||||
return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath);
|
||||
Path jdkPathLin = jvmDir.resolve("jre").resolve("lib").resolve(name.concat(".jar"));
|
||||
return IOHelper.isFile(jrePath) || IOHelper.isFile(jdkPath) || IOHelper.isFile(jdkPathLin) || IOHelper.isFile(jrePathLin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
* [See license](LICENSE)
|
||||
* [See code of conduct](CODE_OF_CONDUCT.md)
|
||||
* [WIKI](https://launcher.gravit.pro)
|
||||
* [WIKI](https://gravitlauncher.com)
|
||||
* Get it (requires cURL):
|
||||
|
||||
```sh
|
||||
|
|
|
@ -64,7 +64,7 @@ pack project(':LauncherAPI')
|
|||
|
||||
shadowJar {
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
classifier = null
|
||||
archiveClassifier = null
|
||||
relocate 'io.netty', 'pro.gravit.repackage.io.netty'
|
||||
configurations = [project.configurations.pack]
|
||||
exclude 'module-info.class'
|
||||
|
@ -82,7 +82,7 @@ pack project(':LauncherAPI')
|
|||
pom {
|
||||
name = 'GravitLauncher ServerWrapper API'
|
||||
description = 'GravitLauncher ServerWrapper Module API'
|
||||
url = 'https://launcher.gravit.pro'
|
||||
url = 'https://gravitlauncher.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU General Public License, Version 3.0'
|
||||
|
@ -105,7 +105,7 @@ pack project(':LauncherAPI')
|
|||
scm {
|
||||
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
|
||||
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
|
||||
url = 'https://launcher.gravit.pro/'
|
||||
url = 'https://gravitlauncher.com/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,9 +180,9 @@ public void run(String... args) throws Throwable {
|
|||
break;
|
||||
}
|
||||
LogHelper.info("Start Minecraft Server");
|
||||
LogHelper.debug("Invoke main method %s with %s", config.mainclass, launch.getClass().getName());
|
||||
LogHelper.debug("Invoke main method %s with %s", classname, launch.getClass().getName());
|
||||
try {
|
||||
launch.run(config, real_args);
|
||||
launch.run(classname, config, real_args);
|
||||
} catch (Throwable e) {
|
||||
LogHelper.error(e);
|
||||
System.exit(-1);
|
||||
|
|
|
@ -20,6 +20,7 @@ public class InstallAuthlib {
|
|||
modifierMap.put("META-INF/libraries.list", new LibrariesLstModifier());
|
||||
modifierMap.put("patch.properties", new PatchPropertiesModifier());
|
||||
modifierMap.put("META-INF/download-context", new DownloadContextModifier());
|
||||
modifierMap.put("META-INF/patches.list", new PatchesLstModifier());
|
||||
}
|
||||
public void run(String... args) throws Exception {
|
||||
boolean deleteAuthlibAfterInstall = false;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package pro.gravit.launcher.server.authlib;
|
||||
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class PatchesLstModifier implements LibrariesHashFileModifier {
|
||||
@Override
|
||||
public byte[] apply(byte[] data, InstallAuthlib.InstallAuthlibContext context) throws IOException {
|
||||
String[] lines = new String(data).split("\n");
|
||||
for(int i=0;i<lines.length;++i) {
|
||||
if(lines[i].contains("paper-")) {
|
||||
String[] separated = lines[i].split("\t");
|
||||
Path path = context.workdir.resolve("versions").resolve(separated[6]);
|
||||
if(Files.notExists(path)) {
|
||||
LogHelper.warning("Unable to find %s. Maybe you should start the server at least once?", path);
|
||||
return data;
|
||||
}
|
||||
separated[3] = SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, path));
|
||||
lines[i] = String.join("\t", separated);
|
||||
}
|
||||
}
|
||||
return String.join("\n", lines).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
|
@ -13,10 +13,10 @@
|
|||
public class ClasspathLaunch implements Launch {
|
||||
@Override
|
||||
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
|
||||
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
|
||||
ClassLoader ucl = new PublicURLClassLoader(urls);
|
||||
Class<?> mainClass = Class.forName(config.mainclass, true, ucl);
|
||||
Class<?> mainClass = Class.forName(mainclass, true, ucl);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
|
||||
mainMethod.invoke(args);
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
import pro.gravit.launcher.server.ServerWrapper;
|
||||
|
||||
public interface Launch {
|
||||
void run(ServerWrapper.Config config, String[] args) throws Throwable;
|
||||
void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
public class ModuleLaunch implements Launch {
|
||||
@Override
|
||||
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
throw new UnsupportedOperationException("Module system not supported");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
public class SimpleLaunch implements Launch {
|
||||
@Override
|
||||
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
|
||||
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
Class<?> mainClass = Class.forName(config.mainclass);
|
||||
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
Class<?> mainClass = Class.forName(mainclass);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
|
||||
mainMethod.invoke(args);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModuleLaunch implements Launch {
|
||||
@Override
|
||||
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
|
||||
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
|
||||
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
|
||||
ClassLoader ucl = new PublicURLClassLoader(urls);
|
||||
// Create Module Layer
|
||||
|
@ -55,7 +54,7 @@ public void run(ServerWrapper.Config config, String[] args) throws Throwable {
|
|||
}
|
||||
// Start main class
|
||||
ClassLoader loader = mainModule.getClassLoader();
|
||||
Class<?> mainClass = Class.forName(config.mainclass, true, loader);
|
||||
Class<?> mainClass = Class.forName(mainclass, true, loader);
|
||||
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
|
||||
mainMethod.invoke(args);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2' apply false
|
||||
id 'maven-publish'
|
||||
id 'signing'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.10' apply false
|
||||
}
|
||||
group = 'pro.gravit.launcher'
|
||||
version = '5.3.0'
|
||||
version = '5.3.6'
|
||||
|
||||
apply from: 'props.gradle'
|
||||
|
||||
|
|
2
modules
2
modules
|
@ -1 +1 @@
|
|||
Subproject commit eb3145d3e75ff3557fe9f9fc87dd5ea0737ff3a2
|
||||
Subproject commit e16960e7eef248a217071638f0e68b14cec09cb8
|
22
props.gradle
22
props.gradle
|
@ -1,20 +1,20 @@
|
|||
project.ext {
|
||||
verAsm = '9.3'
|
||||
verNetty = '4.1.78.Final'
|
||||
verOshiCore = '6.2.1'
|
||||
verJunit = '5.8.2'
|
||||
verAsm = '9.4'
|
||||
verNetty = '4.1.87.Final'
|
||||
verOshiCore = '6.4.0'
|
||||
verJunit = '5.9.2'
|
||||
verGuavaC = '30.1.1-jre'
|
||||
verJansi = '2.4.0'
|
||||
verJline = '3.21.0'
|
||||
verJline = '3.22.0'
|
||||
verJwt = '0.11.5'
|
||||
verBcprov = '1.70'
|
||||
verGson = '2.9.0'
|
||||
verGson = '2.10.1'
|
||||
verBcpkix = '1.70'
|
||||
verSlf4j = '1.7.36'
|
||||
verLog4j = '2.17.2'
|
||||
verMySQLConn = '8.0.29'
|
||||
verPostgreSQLConn = '42.4.0'
|
||||
verProguard = '7.2.2'
|
||||
verLaunch4j = '3.14'
|
||||
verLog4j = '2.19.0'
|
||||
verMySQLConn = '8.0.32'
|
||||
verPostgreSQLConn = '42.5.1'
|
||||
verProguard = '7.3.1'
|
||||
verLaunch4j = '3.50'
|
||||
verHibernate = '5.5.6.Final'
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue