Merge branch 'GravitLauncher:master' into master

This commit is contained in:
Metall 2023-03-27 20:27:50 +05:00 committed by GitHub
commit 8f20cbe104
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
109 changed files with 1410 additions and 1250 deletions

View file

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

View file

@ -186,7 +186,7 @@ task dumpClientLibs(type: Copy) {
pom { pom {
name = 'GravitLauncher LaunchServer API' name = 'GravitLauncher LaunchServer API'
description = 'GravitLauncher LaunchServer Module API' description = 'GravitLauncher LaunchServer Module API'
url = 'https://launcher.gravit.pro' url = 'https://gravitlauncher.com'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -209,7 +209,7 @@ task dumpClientLibs(type: Copy) {
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/' url = 'https://gravitlauncher.com/'
} }
} }
} }

View file

@ -17,25 +17,6 @@ public class HttpRequester {
public 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) { public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz); return new SimpleErrorHandler<>(clazz);
} }
@ -48,7 +29,7 @@ public <T> HttpRequest get(String url, String token) {
.header("Content-Type", "application/json; charset=UTF-8") .header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json") .header("Accept", "application/json")
.timeout(Duration.ofMillis(10000)); .timeout(Duration.ofMillis(10000));
if(token != null) { if (token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token)); requestBuilder.header("Authorization", "Bearer ".concat(token));
} }
return requestBuilder.build(); return requestBuilder.build();
@ -65,7 +46,7 @@ public <T> HttpRequest post(String url, T request, String token) {
.header("Content-Type", "application/json; charset=UTF-8") .header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json") .header("Accept", "application/json")
.timeout(Duration.ofMillis(10000)); .timeout(Duration.ofMillis(10000));
if(token != null) { if (token != null) {
requestBuilder.header("Authorization", "Bearer ".concat(token)); requestBuilder.header("Authorization", "Bearer ".concat(token));
} }
return requestBuilder.build(); return requestBuilder.build();
@ -78,6 +59,26 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
return HttpHelper.send(httpClient, request, makeEH(clazz)); return HttpHelper.send(httpClient, request, makeEH(clazz));
} }
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if (statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
if (type == Void.class) {
return new HttpHelper.HttpOptional<>(null, null, statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public static class SimpleError { public static class SimpleError {
public String error; public String error;
public int code; public int code;

View file

@ -3,6 +3,8 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.Launcher; 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.managers.ConfigManager;
import pro.gravit.launcher.modules.events.ClosePhase; import pro.gravit.launcher.modules.events.ClosePhase;
import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
@ -19,7 +21,9 @@
import pro.gravit.launchserver.manangers.hook.AuthHookManager; import pro.gravit.launchserver.manangers.hook.AuthHookManager;
import pro.gravit.launchserver.modules.events.*; import pro.gravit.launchserver.modules.events.*;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.handlers.NettyServerSocketHandler; 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.launchserver.socket.response.auth.RestoreResponse;
import pro.gravit.utils.command.Command; import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.CommandHandler; import pro.gravit.utils.command.CommandHandler;
@ -63,6 +67,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
* The path to the folder with compile-only libraries for the launcher * The path to the folder with compile-only libraries for the launcher
*/ */
public final Path launcherLibrariesCompile; public final Path launcherLibrariesCompile;
public final Path launcherPack;
/** /**
* The path to the folder with updates/webroot * The path to the folder with updates/webroot
*/ */
@ -133,6 +138,10 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread); this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir; launcherLibraries = directories.launcherLibrariesDir;
launcherLibrariesCompile = directories.launcherLibrariesCompileDir; launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
launcherPack = directories.launcherPackDir;
if(!Files.isDirectory(launcherPack)) {
Files.createDirectories(launcherPack);
}
config.setLaunchServer(this); config.setLaunchServer(this);
@ -374,6 +383,24 @@ public void syncProfilesDir() throws IOException {
// Sort and set new profiles // Sort and set new profiles
newProfies.sort(Comparator.comparing(a -> a)); newProfies.sort(Comparator.comparing(a -> a));
profilesList = Set.copyOf(newProfies); 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 { 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 class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles", public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries", TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", KEY_NAME = ".keys"; LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys";
public Path updatesDir; public Path updatesDir;
public Path profilesDir; public Path profilesDir;
public Path launcherLibrariesDir; public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir; public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
public Path keyDirectory; public Path keyDirectory;
public Path dir; public Path dir;
public Path trustStore; public Path trustStore;
@ -481,6 +509,8 @@ public void collect() {
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME); if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null) if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME); launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if(launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_NAME);
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME); if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
if (tmpDir == null) if (tmpDir == null)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken())); tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));

View file

@ -21,6 +21,7 @@
import pro.gravit.launchserver.manangers.LaunchServerGsonManager; import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager; import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.WebSocketService; import pro.gravit.launchserver.socket.WebSocketService;
import pro.gravit.utils.Version;
import pro.gravit.utils.command.CommandHandler; import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.JLineCommandHandler; import pro.gravit.utils.command.JLineCommandHandler;
import pro.gravit.utils.command.StdCommandHandler; import pro.gravit.utils.command.StdCommandHandler;
@ -28,14 +29,12 @@
import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.io.BufferedReader; import java.io.*;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.List;
public class LaunchServerStarter { public class LaunchServerStarter {
public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned"); public static final boolean allowUnsigned = Boolean.getBoolean("launchserver.allowUnsigned");
@ -89,6 +88,7 @@ public static void main(String[] args) throws Exception {
modulesManager.initModules(null); modulesManager.initModules(null);
registerAll(); registerAll();
initGson(modulesManager); initGson(modulesManager);
printExperimentalBranch();
if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) { if (IOHelper.exists(dir.resolve("LaunchServer.conf"))) {
configFile = dir.resolve("LaunchServer.conf"); configFile = dir.resolve("LaunchServer.conf");
} else { } else {
@ -148,24 +148,34 @@ public LaunchServerRuntimeConfig readRuntimeConfig() throws IOException {
@Override @Override
public void writeConfig(LaunchServerConfig config) throws IOException { 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) { if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer); Launcher.gsonManager.configGson.toJson(config, writer);
} else { } else {
logger.error("Error writing LaunchServer runtime config file. Gson is null"); logger.error("Error writing LaunchServer config file. Gson is null");
} }
} }
byte[] bytes = output.toByteArray();
if(bytes.length > 0) {
IOHelper.write(configFile, bytes);
}
} }
@Override @Override
public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException { public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOException {
try (Writer writer = IOHelper.newWriter(runtimeConfigFile)) { ByteArrayOutputStream output = new ByteArrayOutputStream();
try (Writer writer = IOHelper.newWriter(output)) {
if (Launcher.gsonManager.configGson != null) { if (Launcher.gsonManager.configGson != null) {
Launcher.gsonManager.configGson.toJson(config, writer); Launcher.gsonManager.configGson.toJson(config, writer);
} else { } else {
logger.error("Error writing LaunchServer runtime config file. Gson is null"); 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(); LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
@ -206,6 +216,26 @@ public static void registerAll() {
OptionalTrigger.registerProviders(); 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 { public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException {
if (IOHelper.isFile(configFile)) if (IOHelper.isFile(configFile))
return; return;

View file

@ -22,6 +22,7 @@ public final class AuthProviderPair {
public transient String name; public transient String name;
public transient Set<String> features; public transient Set<String> features;
public String displayName; public String displayName;
public boolean visible = true;
private transient boolean warnOAuthShow = false; private transient boolean warnOAuthShow = false;
public AuthProviderPair() { public AuthProviderPair() {
@ -38,15 +39,6 @@ public static Set<String> getFeatures(Class<?> clazz) {
return list; 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) { public static void getFeatures(Class<?> clazz, Set<String> list) {
Features features = clazz.getAnnotation(Features.class); Features features = clazz.getAnnotation(Features.class);
if (features != null) { 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) { public final <T> T isSupport(Class<T> clazz) {
if (core == null) return null; if (core == null) return null;
T result = null; T result = null;

View file

@ -11,7 +11,7 @@
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
public final class MySQLSourceConfig implements AutoCloseable { public final class MySQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))),

View file

@ -10,7 +10,7 @@
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
public final class PostgreSQLSourceConfig implements AutoCloseable { public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt( public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))), Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000"); VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");
@ -28,8 +28,8 @@ public final class PostgreSQLSourceConfig implements AutoCloseable {
private String database; private String database;
// Cache // Cache
private DataSource source; private transient DataSource source;
private boolean hikari; private transient boolean hikari;
@Override @Override
public synchronized void close() { public synchronized void close() {

View file

@ -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();
}

View file

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

View file

@ -46,6 +46,7 @@ public static void registerProviders() {
providers.register("postgresql", PostgresSQLCoreProvider.class); providers.register("postgresql", PostgresSQLCoreProvider.class);
providers.register("memory", MemoryAuthCoreProvider.class); providers.register("memory", MemoryAuthCoreProvider.class);
providers.register("http", HttpAuthCoreProvider.class); providers.register("http", HttpAuthCoreProvider.class);
providers.register("merge", MergeAuthCoreProvider.class);
registredProviders = true; registredProviders = true;
} }
} }
@ -91,7 +92,7 @@ public Map<String, Command> getCommands() {
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 1); verifyArgs(args, 1);
AuthRequest.AuthPasswordInterface password = null; AuthRequest.AuthPasswordInterface password = null;
if(args.length > 1) { if (args.length > 1) {
if (args[1].startsWith("{")) { if (args[1].startsWith("{")) {
password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class); password = Launcher.gsonManager.gson.fromJson(args[1], AuthRequest.AuthPasswordInterface.class);
} else { } else {

View file

@ -1,5 +1,6 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions; import pro.gravit.launcher.ClientPermissions;
@ -13,6 +14,7 @@
import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportRemoteClientAccess;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware; 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.UserSupportProperties;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures;
@ -25,9 +27,8 @@
import java.io.IOException; import java.io.IOException;
import java.util.*; 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 final Logger logger = LogManager.getLogger();
private transient HttpRequester requester;
public String bearerToken; public String bearerToken;
public String getUserByUsernameUrl; public String getUserByUsernameUrl;
public String getUserByLoginUrl; public String getUserByLoginUrl;
@ -49,6 +50,9 @@ public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSuppor
public String getUsersByHardwareInfoUrl; public String getUsersByHardwareInfoUrl;
public String banHardwareUrl; public String banHardwareUrl;
public String unbanHardwareUrl; public String unbanHardwareUrl;
public String apiUrl;
public List<String> apiFeatures;
private transient HttpRequester requester;
@Override @Override
public User getUserByUsername(String username) { public User getUserByUsername(String username) {
@ -62,7 +66,7 @@ public User getUserByUsername(String username) {
@Override @Override
public User getUserByLogin(String login) { public User getUserByLogin(String login) {
if(getUserByLoginUrl != null) { if (getUserByLoginUrl != null) {
try { try {
return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow(); return requester.send(requester.get(CommonHelper.replace(getUserByLoginUrl, "login", login), null), HttpUser.class).getOrThrow();
} catch (IOException e) { } catch (IOException e) {
@ -85,7 +89,7 @@ public User getUserByUUID(UUID uuid) {
@Override @Override
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) { public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {
if(getAuthDetailsUrl == null) { if (getAuthDetailsUrl == null) {
return super.getDetails(client); return super.getDetails(client);
} }
try { try {
@ -99,14 +103,14 @@ public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(
@Override @Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired { public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
if(getUserByTokenUrl == null) { if (getUserByTokenUrl == null) {
return null; return null;
} }
try { try {
var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class); var result = requester.send(requester.get(getUserByTokenUrl, accessToken), HttpUserSession.class);
if(!result.isSuccessful()) { if (!result.isSuccessful()) {
var error = result.error().error; var error = result.error().error;
if(error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) { if (error.equals(AuthRequestEvent.OAUTH_TOKEN_EXPIRE)) {
throw new OAuthAccessTokenExpired(); throw new OAuthAccessTokenExpired();
} }
return null; return null;
@ -120,7 +124,7 @@ public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws O
@Override @Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) { public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
if(refreshTokenUrl == null) { if (refreshTokenUrl == null) {
return null; return null;
} }
try { try {
@ -136,9 +140,9 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException { public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess), var result = requester.send(requester.post(authorizeUrl, new AuthorizeRequest(login, context, password, minecraftAccess),
bearerToken), HttpAuthReport.class); bearerToken), HttpAuthReport.class);
if(!result.isSuccessful()) { if (!result.isSuccessful()) {
var error = result.error().error; var error = result.error().error;
if(error != null) { if (error != null) {
throw new AuthException(error); throw new AuthException(error);
} }
} }
@ -147,7 +151,7 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
@Override @Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) { public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
if(getHardwareInfoByPublicKeyUrl == null) { if (getHardwareInfoByPublicKeyUrl == null) {
return null; return null;
} }
try { try {
@ -161,7 +165,7 @@ public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
@Override @Override
public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) { public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) {
if(getHardwareInfoByDataUrl == null) { if (getHardwareInfoByDataUrl == null) {
return null; return null;
} }
try { try {
@ -179,7 +183,7 @@ public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo inf
@Override @Override
public UserHardware getHardwareInfoById(String id) { public UserHardware getHardwareInfoById(String id) {
if(getHardwareInfoByIdUrl == null) { if (getHardwareInfoByIdUrl == null) {
return null; return null;
} }
try { try {
@ -193,7 +197,7 @@ public UserHardware getHardwareInfoById(String id) {
@Override @Override
public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey) { public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info, byte[] publicKey) {
if(createHardwareInfoUrl == null) { if (createHardwareInfoUrl == null) {
return null; return null;
} }
try { try {
@ -207,7 +211,7 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo info,
@Override @Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) { public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
if(connectUserAndHardwareUrl == null) { if (connectUserAndHardwareUrl == null) {
return; return;
} }
try { try {
@ -219,24 +223,25 @@ public void connectUserAndHardware(UserSession userSession, UserHardware hardwar
@Override @Override
public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) { public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) {
if(addPublicKeyToHardwareInfoUrl == null) { if (addPublicKeyToHardwareInfoUrl == null) {
return; return;
} }
try { try {
requester.send(requester.post(addPublicKeyToHardwareInfoUrl, new HardwareRequest((HttpUserHardware)hardware, publicKey), bearerToken), Void.class); requester.send(requester.post(addPublicKeyToHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware, publicKey), bearerToken), Void.class);
} catch (IOException e) { } catch (IOException e) {
logger.error(e); logger.error(e);
} }
} }
@SuppressWarnings({"unchecked", "rawtypes"})
@Override @Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) { public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
if(getUsersByHardwareInfoUrl == null) { if (getUsersByHardwareInfoUrl == null) {
return null; return null;
} }
try { try {
return requester.send(requester return (List<User>) (List) requester.send(requester
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), List.class).getOrThrow(); .post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), GetHardwareListResponse.class).getOrThrow().list;
} catch (IOException e) { } catch (IOException e) {
logger.error(e); logger.error(e);
return null; return null;
@ -245,7 +250,7 @@ public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
@Override @Override
public void banHardware(UserHardware hardware) { public void banHardware(UserHardware hardware) {
if(banHardwareUrl == null) { if (banHardwareUrl == null) {
return; return;
} }
try { try {
@ -257,7 +262,7 @@ public void banHardware(UserHardware hardware) {
@Override @Override
public void unbanHardware(UserHardware hardware) { public void unbanHardware(UserHardware hardware) {
if(unbanHardwareUrl == null) { if (unbanHardwareUrl == null) {
return; return;
} }
try { try {
@ -267,12 +272,14 @@ public void unbanHardware(UserHardware hardware) {
} }
} }
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken, @Override
String oauthRefreshToken, long oauthExpire, public String getClientApiUrl() {
HttpUserSession session) { return apiUrl;
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
} }
@Override
public List<String> getClientApiFeatures() {
return apiFeatures;
} }
@Override @Override
@ -293,6 +300,36 @@ public boolean joinServer(Client client, String username, String accessToken, St
return result.isSuccessful(); 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 static class UpdateServerIdRequest {
public String username; public String username;
public UUID uuid; public UUID uuid;
@ -319,6 +356,10 @@ public static class GetAuthDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details; public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
} }
public static class GetHardwareListResponse {
public List<HttpUser> list;
}
public static class JoinServerRequest { public static class JoinServerRequest {
public String username; public String username;
public String accessToken; 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 static class AuthorizeRequest {
public String login; public String login;
public AuthResponse.AuthContext context; public AuthResponse.AuthContext context;
@ -400,153 +419,6 @@ public HardwareRequest(byte[] key) {
} }
public class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
@Deprecated
private Texture skin;
@Deprecated
private Texture cloak;
private Map<String, Texture> assets;
private Map<String, String> properties;
private long hwidId;
private transient HttpUserHardware hardware;
public HttpUser() {
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.properties = properties;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Map<String, Texture> assets, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.assets = assets;
this.properties = properties;
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 Texture getSkinTexture() {
if(assets == null) {
return skin;
}
return assets.get("SKIN");
}
@Override
public Texture getCloakTexture() {
if(assets == null) {
return cloak;
}
return assets.get("CAPE");
}
public Map<String, Texture> getAssets() {
if(assets == null) {
Map<String, Texture> map = new HashMap<>();
if(skin != null) {
map.put("SKIN", skin);
}
if(cloak != null) {
map.put("CAPE", cloak);
}
return map;
}
return assets;
}
@Override
public Map<String, String> getProperties() {
if(properties == null) {
return new HashMap<>();
}
return properties;
}
@Override
public String toString() {
return "HttpUser{" +
"username='" + username + '\'' +
", uuid=" + uuid +
", serverId='" + serverId + '\'' +
", accessToken='" + accessToken + '\'' +
", permissions=" + permissions +
", assets=" + getAssets() +
", properties=" + properties +
", hwidId=" + hwidId +
'}';
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
HttpAuthCoreProvider.HttpUserHardware result = (HttpUserHardware) getHardwareInfoById(String.valueOf(hwidId));
hardware = result;
return result;
}
}
public static class HttpUserSession implements UserSession { public static class HttpUserSession implements UserSession {
private String id; private String id;
private HttpUser user; private HttpUser user;
@ -646,4 +518,151 @@ public String toString() {
'}'; '}';
} }
} }
public class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
private String username;
private UUID uuid;
private String serverId;
private String accessToken;
private ClientPermissions permissions;
@Deprecated
private Texture skin;
@Deprecated
private Texture cloak;
private Map<String, Texture> assets;
private Map<String, String> properties;
private long hwidId;
private transient HttpUserHardware hardware;
public HttpUser() {
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Texture skin, Texture cloak, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.skin = skin;
this.cloak = cloak;
this.properties = properties;
this.hwidId = hwidId;
}
public HttpUser(String username, UUID uuid, String serverId, String accessToken, ClientPermissions permissions, Map<String, Texture> assets, Map<String, String> properties, long hwidId) {
this.username = username;
this.uuid = uuid;
this.serverId = serverId;
this.accessToken = accessToken;
this.permissions = permissions;
this.assets = assets;
this.properties = properties;
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 Texture getSkinTexture() {
if (assets == null) {
return skin;
}
return assets.get("SKIN");
}
@Override
public Texture getCloakTexture() {
if (assets == null) {
return cloak;
}
return assets.get("CAPE");
}
public Map<String, Texture> getAssets() {
if (assets == null) {
Map<String, Texture> map = new HashMap<>();
if (skin != null) {
map.put("SKIN", skin);
}
if (cloak != null) {
map.put("CAPE", cloak);
}
return map;
}
return assets;
}
@Override
public Map<String, String> getProperties() {
if (properties == null) {
return new HashMap<>();
}
return properties;
}
@Override
public String toString() {
return "HttpUser{" +
"username='" + username + '\'' +
", uuid=" + uuid +
", serverId='" + serverId + '\'' +
", accessToken='" + accessToken + '\'' +
", permissions=" + permissions +
", assets=" + getAssets() +
", properties=" + properties +
", hwidId=" + hwidId +
'}';
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
HttpAuthCoreProvider.HttpUserHardware result = (HttpUserHardware) getHardwareInfoById(String.valueOf(hwidId));
hardware = result;
return result;
}
}
} }

View file

@ -13,15 +13,19 @@
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; 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 { public class MemoryAuthCoreProvider extends AuthCoreProvider {
private transient final List<MemoryUser> memory = new ArrayList<>(16); private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override @Override
public User getUserByUsername(String username) { public User getUserByUsername(String username) {
synchronized (memory) { synchronized (memory) {
for(MemoryUser u : memory) { for (MemoryUser u : memory) {
if(u.username.equals(username)) { if (u.username.equals(username)) {
return u; return u;
} }
} }
@ -39,8 +43,8 @@ public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(
@Override @Override
public User getUserByUUID(UUID uuid) { public User getUserByUUID(UUID uuid) {
synchronized (memory) { synchronized (memory) {
for(MemoryUser u : memory) { for (MemoryUser u : memory) {
if(u.uuid.equals(uuid)) { if (u.uuid.equals(uuid)) {
return u; return u;
} }
} }
@ -51,8 +55,8 @@ public User getUserByUUID(UUID uuid) {
@Override @Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired { public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
synchronized (memory) { synchronized (memory) {
for(MemoryUser u : memory) { for (MemoryUser u : memory) {
if(u.accessToken.equals(accessToken)) { if (u.accessToken.equals(accessToken)) {
return new MemoryUserSession(u); return new MemoryUserSession(u);
} }
} }
@ -67,23 +71,23 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon
@Override @Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException { public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
if(login == null) { if (login == null) {
throw AuthException.userNotFound(); throw AuthException.userNotFound();
} }
MemoryUser user = null; MemoryUser user = null;
synchronized (memory) { synchronized (memory) {
for(MemoryUser u : memory) { for (MemoryUser u : memory) {
if(u.username.equals(login)) { if (u.username.equals(login)) {
user = u; user = u;
break; break;
} }
} }
if(user == null) { if (user == null) {
user = new MemoryUser(login); user = new MemoryUser(login);
memory.add(user); memory.add(user);
} }
} }
if(!minecraftAccess) { if (!minecraftAccess) {
return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user)); return AuthManager.AuthReport.ofOAuth(user.accessToken, null, 0, new MemoryUserSession(user));
} else { } else {
return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user)); return AuthManager.AuthReport.ofOAuthWithMinecraft(user.accessToken, user.accessToken, null, 0, new MemoryUserSession(user));
@ -100,8 +104,8 @@ protected boolean updateServerID(User user, String serverID) throws IOException
@Override @Override
public User checkServer(Client client, String username, String serverID) throws IOException { public User checkServer(Client client, String username, String serverID) throws IOException {
synchronized (memory) { synchronized (memory) {
for(MemoryUser u : memory) { for (MemoryUser u : memory) {
if(u.username.equals(username)) { if (u.username.equals(username)) {
return u; return u;
} }
} }

View file

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

View file

@ -1,59 +1,30 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions; import pro.gravit.launcher.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.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig; import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHardware { public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware {
private transient final Logger logger = LogManager.getLogger();
public MySQLSourceConfig mySQLHolder; public MySQLSourceConfig mySQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String hardwareIdColumn; public String hardwareIdColumn;
public String table;
public String tableHWID = "hwids"; public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog"; public String tableHWIDLog = "hwidLog";
public PasswordVerifier passwordVerifier;
public double criticalCompareLevel = 1.0; public double criticalCompareLevel = 1.0;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
private transient String sqlFindHardwareByPublicKey; private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData; private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById; private transient String sqlFindHardwareById;
@ -63,130 +34,16 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
private transient String sqlUpdateHardwareBanned; private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers; private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId; private transient String sqlUsersByHwidId;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override @Override
public User getUserByUsername(String username) { public SQLSourceConfig getSQLConfig() {
try { return mySQLHolder;
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);
}
} }
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
this.server = server; super.init(server);
if (mySQLHolder == null) logger.error("mySQLHolder cannot be null"); String userInfoCols = makeUserCols();
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = String.format("%s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, hardwareIdColumn);
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
table, serverIDColumn, uuidColumn);
String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey"; String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey";
if (sqlFindHardwareByPublicKey == null) if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID); 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); sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
} }
protected boolean updateAuth(User user, String accessToken) throws IOException { @Override
try (Connection c = mySQLHolder.getConnection()) { protected String makeUserCols() {
MySQLUser mySQLUser = (MySQLUser) user; return super.makeUserCols().concat(", ").concat(hardwareIdColumn);
mySQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
} }
@Override @Override
protected boolean updateServerID(User user, String serverID) throws IOException { protected MySQLUser constructUser(ResultSet set) throws SQLException {
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 {
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn), return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), 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 { private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
@ -271,19 +100,6 @@ private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) th
s.executeUpdate(); s.executeUpdate();
} }
private User query(String sql, String value) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override @Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) { public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = mySQLHolder.getConnection()) { try (Connection connection = mySQLHolder.getConnection()) {
@ -371,8 +187,8 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
@Override @Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) { public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession; SQLUserSession mySQLUserSession = (SQLUserSession) userSession;
MySQLUser mySQLUser = mySQLUserSession.user; MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser();
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware; MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
if (mySQLUser.hwidId == mySQLUserHardware.id) return; if (mySQLUser.hwidId == mySQLUserHardware.id) return;
mySQLUser.hwidId = mySQLUserHardware.id; mySQLUser.hwidId = mySQLUserHardware.id;
@ -488,51 +304,15 @@ public String toString() {
} }
} }
public class MySQLUser implements User, UserSupportHardware { public class MySQLUser extends SQLUser implements UserSupportHardware {
protected UUID uuid;
protected String username;
protected String accessToken;
protected String serverId;
protected String password;
protected ClientPermissions permissions;
protected long hwidId; protected long hwidId;
protected transient MySQLUserHardware hardware; protected transient MySQLUserHardware hardware;
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) { public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
this.uuid = uuid; super(uuid, username, accessToken, serverId, password, permissions);
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
this.hwidId = hwidId; this.hwidId = hwidId;
} }
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override @Override
public UserHardware getHardware() { public UserHardware getHardware() {
if (hardware != null) return hardware; 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;
}
}
} }

View file

@ -1,297 +1,13 @@
package pro.gravit.launchserver.auth.core; package pro.gravit.launchserver.auth.core;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig; import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.SQLSourceConfig;
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; public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
import java.sql.*;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.UUID;
public class PostgresSQLCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
public PostgreSQLSourceConfig postgresSQLHolder; public PostgreSQLSourceConfig postgresSQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String table;
public PasswordVerifier passwordVerifier;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override @Override
public User getUserByUsername(String username) { public SQLSourceConfig getSQLConfig() {
try { return postgresSQLHolder;
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;
}
} }
} }

View file

@ -0,0 +1,9 @@
package pro.gravit.launchserver.auth.core.interfaces.provider;
import java.util.List;
public interface AuthSupportRemoteClientAccess {
String getClientApiUrl();
List<String> getClientApiFeatures();
}

View file

@ -5,7 +5,9 @@
public interface UserSessionSupportKeys { public interface UserSessionSupportKeys {
ClientProfileKeys getClientProfileKeys(); 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) {
} }
} }

View file

@ -23,10 +23,10 @@ default Map<String, Texture> getUserAssets() {
var skin = getSkinTexture(); var skin = getSkinTexture();
var cape = getCloakTexture(); var cape = getCloakTexture();
Map<String, Texture> map = new HashMap<>(); Map<String, Texture> map = new HashMap<>();
if(skin != null) { if (skin != null) {
map.put("SKIN", skin); map.put("SKIN", skin);
} }
if(cape != null) { if (cape != null) {
map.put("CAPE", cape); map.put("CAPE", cape);
} }
return map; return map;

View file

@ -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);
}
}

View file

@ -19,29 +19,6 @@ public class JsonPasswordVerifier extends PasswordVerifier {
public String url; public String url;
public String bearerToken; public String bearerToken;
@Override
public boolean check(String encryptedPassword, String password) {
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
if (response != null) {
return response.success;
}
return false;
}
public static class JsonPasswordRequest {
public String encryptedPassword;
public String password;
public JsonPasswordRequest(String encryptedPassword, String password) {
this.encryptedPassword = encryptedPassword;
this.password = password;
}
}
public static class JsonPasswordResponse {
public boolean success;
}
public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) { public static <T, R> R jsonRequest(T request, String url, String bearerToken, Class<R> clazz, HttpClient client) {
HttpRequest.BodyPublisher publisher; HttpRequest.BodyPublisher publisher;
if (request != null) { if (request != null) {
@ -78,4 +55,27 @@ public static <T, R> R jsonRequest(T request, String url, String bearerToken, Cl
return null; return null;
} }
} }
@Override
public boolean check(String encryptedPassword, String password) {
JsonPasswordResponse response = jsonRequest(new JsonPasswordRequest(encryptedPassword, password), url, bearerToken, JsonPasswordResponse.class, client);
if (response != null) {
return response.success;
}
return false;
}
public static class JsonPasswordRequest {
public String encryptedPassword;
public String password;
public JsonPasswordRequest(String encryptedPassword, String password) {
this.encryptedPassword = encryptedPassword;
this.password = password;
}
}
public static class JsonPasswordResponse {
public boolean success;
}
} }

View file

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

View file

@ -7,7 +7,6 @@
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent; import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.events.request.HardwareReportRequestEvent; import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent; import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
@ -157,9 +156,9 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken
var parse = parser.parseClaimsJws(extendedToken); var parse = parser.parseClaimsJws(extendedToken);
String hardwareInfoId = parse.getBody().get("hardware", String.class); String hardwareInfoId = parse.getBody().get("hardware", String.class);
if (hardwareInfoId == null) return false; if (hardwareInfoId == null) return false;
if(client.auth == null) return false; if (client.auth == null) return false;
var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class); var hardwareSupport = client.auth.core.isSupport(AuthSupportHardware.class);
if(hardwareSupport == null) return false; if (hardwareSupport == null) return false;
UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId); UserHardware hardware = hardwareSupport.getHardwareInfoById(hardwareInfoId);
if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel(); if (client.trustLevel == null) client.trustLevel = new Client.TrustLevel();
client.trustLevel.hardwareInfo = hardware.getHardwareInfo(); client.trustLevel.hardwareInfo = hardware.getHardwareInfo();

View file

@ -27,7 +27,7 @@ public void checkLaunchServerLicense() {
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
if(profileWhitelist != null && profileWhitelist.size() > 0) { if (profileWhitelist != null && profileWhitelist.size() > 0) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'"); logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
} }
} }
@ -48,13 +48,13 @@ public boolean canGetUpdates(String updatesDirName, Client client) {
} }
private boolean isWhitelisted(String property, ClientProfile profile, Client client) { private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if(client.permissions != null) { if (client.permissions != null) {
String permByUUID = String.format(property, profile.getUUID()); String permByUUID = String.format(property, profile.getUUID());
if(client.permissions.hasPerm(permByUUID)) { if (client.permissions.hasPerm(permByUUID)) {
return true; return true;
} }
String permByTitle = String.format(property, profile.getTitle().toLowerCase(Locale.ROOT)); String permByTitle = String.format(property, profile.getTitle().toLowerCase(Locale.ROOT));
if(client.permissions.hasPerm(permByTitle)) { if (client.permissions.hasPerm(permByTitle)) {
return true; return true;
} }
} }

View file

@ -15,9 +15,10 @@
import java.util.UUID; import java.util.UUID;
public class JsonTextureProvider extends TextureProvider { 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 final Logger logger = LogManager.getLogger();
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {}.getType(); public String url;
@Override @Override
public void close() throws IOException { public void close() throws IOException {
@ -42,14 +43,14 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client))); var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client)));
Map<String, Texture> map = Launcher.gsonManager.gson.fromJson(result, MAP_TYPE); Map<String, Texture> map = Launcher.gsonManager.gson.fromJson(result, MAP_TYPE);
if(map == null) { if (map == null) {
return new HashMap<>(); return new HashMap<>();
} }
if(map.get("skin") != null) { // Legacy script if (map.get("skin") != null) { // Legacy script
map.put("SKIN", map.get("skin")); map.put("SKIN", map.get("skin"));
map.remove("skin"); map.remove("skin");
} }
if(map.get("cloak") != null) { if (map.get("cloak") != null) {
map.put("CAPE", map.get("cloak")); map.put("CAPE", map.get("cloak"));
map.remove("cloak"); map.remove("cloak");
} }

View file

@ -33,17 +33,6 @@ public static void registerProviders() {
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException; public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
@Deprecated @Deprecated
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) { public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
@ -83,13 +72,24 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
} }
Map<String, Texture> map = new HashMap<>(); Map<String, Texture> map = new HashMap<>();
if(skin != null) { if (skin != null) {
map.put("SKIN", skin); map.put("SKIN", skin);
} }
if(cloak != null) { if (cloak != null) {
map.put("CAPE", cloak); map.put("CAPE", cloak);
} }
return map; return map;
} }
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
} }

View file

@ -211,7 +211,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
try { try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec); cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, iKeySpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
InvalidAlgorithmParameterException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) { try (OutputStream stream = new CipherOutputStream(new NoCloseOutputStream(output), cipher)) {

View file

@ -8,7 +8,9 @@
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public final class JARLauncherBinary extends LauncherBinary { public final class JARLauncherBinary extends LauncherBinary {
@ -19,6 +21,8 @@ public final class JARLauncherBinary extends LauncherBinary {
public final List<Path> coreLibs; public final List<Path> coreLibs;
public final List<Path> addonLibs; public final List<Path> addonLibs;
public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException { public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar"); super(server, resolve(server, ".jar"), "Launcher-%s-%d.jar");
count = new AtomicLong(0); count = new AtomicLong(0);
@ -27,6 +31,7 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
buildDir = server.dir.resolve("build"); buildDir = server.dir.resolve("build");
coreLibs = new ArrayList<>(); coreLibs = new ArrayList<>();
addonLibs = new ArrayList<>(); addonLibs = new ArrayList<>();
files = new HashMap<>();
if (!Files.isDirectory(buildDir)) { if (!Files.isDirectory(buildDir)) {
Files.deleteIfExists(buildDir); Files.deleteIfExists(buildDir);
Files.createDirectory(buildDir); Files.createDirectory(buildDir);

View file

@ -4,9 +4,12 @@
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; 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, srv.launcherBinary.coreLibs);
attach(output, inputFile, jars); attach(output, inputFile, jars);
for(var entry : srv.launcherBinary.files.entrySet()) {
ZipEntry newEntry = IOHelper.newZipEntry(entry.getKey());
output.putNextEntry(newEntry);
IOHelper.transfer(entry.getValue(), output);
}
} }
return outputFile; return outputFile;
} }

View file

@ -13,6 +13,8 @@
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PrepareBuildTask implements LauncherBuildTask { public class PrepareBuildTask implements LauncherBuildTask {
private final LaunchServer server; private final LaunchServer server;
@ -33,8 +35,19 @@ public String getName() {
public Path process(Path inputFile) throws IOException { public Path process(Path inputFile) throws IOException {
server.launcherBinary.coreLibs.clear(); server.launcherBinary.coreLibs.clear();
server.launcherBinary.addonLibs.clear(); server.launcherBinary.addonLibs.clear();
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), true); server.launcherBinary.files.clear();
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), true); 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); UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
tryUnpack(); tryUnpack();
return result; return result;

View file

@ -83,8 +83,7 @@ private Path setConfig() {
jre.setMinVersion(server.config.launch4j.minVersion); jre.setMinVersion(server.config.launch4j.minVersion);
if (server.config.launch4j.setMaxVersion) if (server.config.launch4j.setMaxVersion)
jre.setMaxVersion(server.config.launch4j.maxVersion); jre.setMaxVersion(server.config.launch4j.maxVersion);
jre.setRuntimeBits(Jre.RUNTIME_BITS_64_AND_32); jre.setPath(System.getProperty("java.home"));
jre.setJdkPreference(Jre.JDK_PREFERENCE_PREFER_JRE);
config.setJre(jre); config.setJre(jre);
// Prepare version info (product) // Prepare version info (product)

View file

@ -11,6 +11,7 @@
public class DebugCommand extends Command { public class DebugCommand extends Command {
private transient Logger logger = LogManager.getLogger(); private transient Logger logger = LogManager.getLogger();
public DebugCommand(LaunchServer server) { public DebugCommand(LaunchServer server) {
super(server); super(server);
} }
@ -34,7 +35,7 @@ public void invoke(String... args) throws Exception {
LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit"); LoggerConfig loggerConfig = config.getLoggerConfig("pro.gravit");
loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG); loggerConfig.setLevel(value ? Level.TRACE : Level.DEBUG);
ctx.updateLoggers(); ctx.updateLoggers();
if(value) { if (value) {
logger.info("Log level TRACE enabled"); logger.info("Log level TRACE enabled");
} else { } else {
logger.info("Log level TRACE disabled"); logger.info("Log level TRACE disabled");

View file

@ -10,9 +10,7 @@
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.Downloader; import pro.gravit.utils.Downloader;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import proguard.OutputWriter;
import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -22,11 +20,9 @@
import java.util.List; import java.util.List;
public final class DownloadAssetCommand extends Command { 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 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 static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
private transient final Logger logger = LogManager.getLogger();
public DownloadAssetCommand(LaunchServer server) { public DownloadAssetCommand(LaunchServer server) {
super(server); super(server);
@ -44,31 +40,31 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 2); verifyArgs(args, 1);
//Version version = Version.byName(args[0]); //Version version = Version.byName(args[0]);
String versionName = args[0]; String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : "assets");
String type = args.length > 2 ? args[2] : "mojang"; String type = args.length > 2 ? args[2] : "mojang";
String dirName = IOHelper.verifyFileName(args[1]);
Path assetDir = server.updatesDir.resolve(dirName); Path assetDir = server.updatesDir.resolve(dirName);
// Create asset dir // Create asset dir
if(Files.notExists(assetDir)) { if (Files.notExists(assetDir)) {
logger.info("Creating asset dir: '{}'", dirName); logger.info("Creating asset dir: '{}'", dirName);
Files.createDirectory(assetDir); Files.createDirectory(assetDir);
} }
if(type.equals("mojang")) { if (type.equals("mojang")) {
HttpRequester requester = new HttpRequester(); HttpRequester requester = new HttpRequester();
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL); logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL);
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow(); var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null; String profileUrl = null;
for(var e : versions.versions) { for (var e : versions.versions) {
if(e.id.equals(versionName)) { if (e.id.equals(versionName)) {
profileUrl = e.url; profileUrl = e.url;
break; break;
} }
} }
if(profileUrl == null) { if (profileUrl == null) {
logger.error("Version {} not found", versionName); logger.error("Version {} not found", versionName);
return; return;
} }
@ -76,30 +72,30 @@ public void invoke(String... args) throws Exception {
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow(); var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url; String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id; String assetIndex = profileInfo.assetIndex.id;
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex+".json"); Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json");
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl); logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow(); JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
JsonObject objects = assets.get("objects").getAsJsonObject(); JsonObject objects = assets.get("objects").getAsJsonObject();
try(Writer writer = IOHelper.newWriter(indexPath)) { try (Writer writer = IOHelper.newWriter(indexPath)) {
logger.info("Save {}", indexPath); logger.info("Save {}", indexPath);
Launcher.gsonManager.configGson.toJson(assets, writer); Launcher.gsonManager.configGson.toJson(assets, writer);
} }
if(!assetIndex.equals(versionName)) { if (!assetIndex.equals(versionName)) {
Path targetPath = assetDir.resolve("indexes").resolve(versionName+".json"); Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json");
logger.info("Copy {} into {}", indexPath, targetPath); logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING); Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
} }
List<AsyncDownloader.SizedFile> toDownload = new ArrayList<>(128); List<AsyncDownloader.SizedFile> toDownload = new ArrayList<>(128);
for(var e : objects.entrySet()) { for (var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject(); var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString(); var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash; hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong(); var size = value.get("size").getAsLong();
var path = "objects/" + hash; var path = "objects/" + hash;
var target = assetDir.resolve(path); var target = assetDir.resolve(path);
if(Files.exists(target)) { if (Files.exists(target)) {
long fileSize = Files.size(target); long fileSize = Files.size(target);
if(fileSize != size) { if (fileSize != size) {
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize); logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
} else { } else {
continue; continue;

View file

@ -41,18 +41,14 @@ public void invoke(String... args) throws IOException, CommandException {
verifyArgs(args, 2); verifyArgs(args, 2);
//Version version = Version.byName(args[0]); //Version version = Version.byName(args[0]);
String versionName = args[0]; String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1]); String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
Path clientDir = server.updatesDir.resolve(args[1]); Path clientDir = server.updatesDir.resolve(dirName);
boolean isMirrorClientDownload = false; boolean isMirrorClientDownload = false;
if (args.length > 2) { if (args.length > 2) {
isMirrorClientDownload = args[2].equals("mirror"); isMirrorClientDownload = args[2].equals("mirror");
} }
// Create client dir
logger.info("Creating client dir: '{}'", dirName);
Files.createDirectory(clientDir);
// Download required client // Download required client
logger.info("Downloading client, it may take some time"); logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir); //HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
@ -60,7 +56,25 @@ public void invoke(String... args) throws IOException, CommandException {
// Create profile file // Create profile file
logger.info("Creaing profile file: '{}'", dirName); logger.info("Creaing profile file: '{}'", dirName);
ClientProfile 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) { if (!isMirrorClientDownload) {
try { try {
String internalVersion = versionName; String internalVersion = versionName;
@ -75,27 +89,14 @@ public void invoke(String... args) throws IOException, CommandException {
for (MakeProfileHelper.MakeProfileOption option : options) { for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName()); logger.debug("Detected option {}", option.getClass().getSimpleName());
} }
client = MakeProfileHelper.makeProfile(version, dirName, options); clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) { } catch (Throwable e) {
isMirrorClientDownload = true; 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, try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
dirName, "json"))) { dirName, "json"))) {
Launcher.gsonManager.configGson.toJson(client, writer); Launcher.gsonManager.configGson.toJson(clientProfile, writer);
} }
// Finished // Finished

View file

@ -11,6 +11,7 @@
public class TokenCommand extends Command { public class TokenCommand extends Command {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public TokenCommand(LaunchServer server) { public TokenCommand(LaunchServer server) {
super(server); super(server);
this.childCommands.put("info", new SubCommand("[token]", "print token info") { this.childCommands.put("info", new SubCommand("[token]", "print token info") {
@ -27,16 +28,16 @@ public void invoke(String... args) throws Exception {
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair(); AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
ClientProfile profile = null; ClientProfile profile = null;
for(ClientProfile p : server.getProfiles()) { for (ClientProfile p : server.getProfiles()) {
if(p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) { if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) {
profile = p; profile = p;
break; break;
} }
} }
if(profile == null) { if (profile == null) {
logger.warn("Profile {} not found", args[0]); logger.warn("Profile {} not found", args[0]);
} }
if(pair == null) { if (pair == null) {
logger.error("AuthId {} not found", args[1]); logger.error("AuthId {} not found", args[1]);
return; return;
} }

View file

@ -13,7 +13,6 @@
import pro.gravit.utils.helper.UnpackHelper; import pro.gravit.utils.helper.UnpackHelper;
import proguard.Configuration; import proguard.Configuration;
import proguard.ConfigurationParser; import proguard.ConfigurationParser;
import proguard.ParseException;
import proguard.ProGuard; import proguard.ProGuard;
import java.io.IOException; import java.io.IOException;

View file

@ -45,7 +45,7 @@ public final class LaunchServerConfig {
public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
LaunchServerConfig newConfig = new LaunchServerConfig(); 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 = new LaunchServerConfig.ExeConf();
newConfig.launch4j.enabled = false; newConfig.launch4j.enabled = false;
newConfig.launch4j.copyright = "© GravitLauncher Team"; newConfig.launch4j.copyright = "© GravitLauncher Team";
@ -166,11 +166,14 @@ public void verify() {
// Mirror check // Mirror check
{ {
boolean updateMirror = Boolean.getBoolean("launchserver.config.disableUpdateMirror"); boolean updateMirror = Boolean.getBoolean("launchserver.config.disableUpdateMirror");
if(!updateMirror) { if (!updateMirror) {
for(int i=0;i < mirrors.length;++i) { for (int i = 0; i < mirrors.length; ++i) {
if("https://mirror.gravit.pro/5.2.x/".equals(mirrors[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'"); 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.gravit.pro/5.3.x/"; 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 ipForwarding;
public boolean disableWebApiInterface; public boolean disableWebApiInterface;
public boolean showHiddenFiles; public boolean showHiddenFiles;
public boolean sendProfileUpdatesEvent = true;
public String launcherURL; public String launcherURL;
public String downloadURL; public String downloadURL;
public String launcherEXEURL; public String launcherEXEURL;

View file

@ -23,11 +23,61 @@
public final class HttpHelper { public final class HttpHelper {
private static transient final Logger logger = LogManager.getLogger(); private static transient final Logger logger = LogManager.getLogger();
private HttpHelper() { private HttpHelper() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public static class HttpOptional<T,E> { 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());
return handler.apply(response);
} catch (InterruptedException e) {
throw new IOException(e);
}
}
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);
}
public static <T> HttpResponse.BodyHandler<T> ofJsonResult(Class<T> type) {
return ofJsonResult((Type) type);
}
public static <T> HttpResponse.BodyHandler<T> ofJsonResult(Type type) {
return new JsonBodyHandler<>(HttpResponse.BodyHandlers.ofInputStream(), (input) -> {
try (Reader reader = new InputStreamReader(input)) {
return Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
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 T result;
protected final E error; protected final E error;
protected final int statusCode; protected final int statusCode;
@ -41,17 +91,21 @@ public HttpOptional(T result, E error, int statusCode) {
public T result() { public T result() {
return result; return result;
} }
public E error() { public E error() {
return error; return error;
} }
public int statusCode() { public int statusCode() {
return statusCode; return statusCode;
} }
public boolean isSuccessful() { public boolean isSuccessful() {
return statusCode >= 200 && statusCode < 300; return statusCode >= 200 && statusCode < 300;
} }
public T getOrThrow() throws RequestException { public T getOrThrow() throws RequestException {
if(isSuccessful()) { if (isSuccessful()) {
return result; return result;
} else { } else {
throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString()); throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString());
@ -59,22 +113,6 @@ public T getOrThrow() throws RequestException {
} }
} }
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> { public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> {
private final Class<T> type; private final Class<T> type;
@ -88,38 +126,6 @@ public HttpOptional<T, Void> applyJson(JsonElement response, int 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());
return handler.apply(response);
} catch (InterruptedException e) {
throw new IOException(e);
}
}
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);
}
public static<T> HttpResponse.BodyHandler<T> ofJsonResult(Class<T> type) {
return ofJsonResult((Type) type);
}
public static<T> HttpResponse.BodyHandler<T> ofJsonResult(Type type) {
return new JsonBodyHandler<>(HttpResponse.BodyHandlers.ofInputStream(), (input) -> {
try(Reader reader = new InputStreamReader(input)) {
return Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
public static<T> HttpRequest.BodyPublisher jsonBodyPublisher(T obj) {
return HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(obj));
}
private static class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T> { private static class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T> {
private final HttpResponse.BodyHandler<InputStream> delegate; private final HttpResponse.BodyHandler<InputStream> delegate;
private final Function<InputStream, T> func; private final Function<InputStream, T> func;

View file

@ -7,7 +7,6 @@
import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.time.Clock; import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Date; import java.util.Date;
@ -38,7 +37,7 @@ public static JwtTokenInfo getJwtInfoFromAccessToken(String token, ECPublicKey p
} }
public static String makeRefreshTokenFromPassword(String username, String rawPassword, String secretSalt) { public static String makeRefreshTokenFromPassword(String username, String rawPassword, String secretSalt) {
if(rawPassword == null) { if (rawPassword == null) {
rawPassword = ""; rawPassword = "";
} }
return SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, return SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,

View file

@ -18,7 +18,7 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
ClientProfileBuilder builder = new ClientProfileBuilder(); ClientProfileBuilder builder = new ClientProfileBuilder();
builder.setVersion(version.name); builder.setVersion(version.name);
builder.setDir(title); builder.setDir(title);
if(findOption(options, MakeProfileOptionGlobalAssets.class).isPresent()) { if (findOption(options, MakeProfileOptionGlobalAssets.class).isPresent()) {
builder.setAssetDir("assets"); builder.setAssetDir("assets");
} else { } else {
builder.setAssetDir("asset" + version.name); builder.setAssetDir("asset" + version.name);
@ -46,10 +46,10 @@ public static ClientProfile makeProfile(ClientProfile.Version version, String ti
Set<OptionalFile> optionals = new HashSet<>(); Set<OptionalFile> optionals = new HashSet<>();
jvmArgs.add("-XX:+DisableAttachMechanism"); jvmArgs.add("-XX:+DisableAttachMechanism");
// Official Mojang launcher java arguments // Official Mojang launcher java arguments
if(version.compareTo(ClientProfile.Version.MC112) <= 0) { if (version.compareTo(ClientProfile.Version.MC112) <= 0) {
jvmArgs.add("-XX:+UseConcMarkSweepGC"); jvmArgs.add("-XX:+UseConcMarkSweepGC");
jvmArgs.add("-XX:+CMSIncrementalMode"); jvmArgs.add("-XX:+CMSIncrementalMode");
} else if(version.compareTo(ClientProfile.Version.MC118) <= 0) { // 1.13 - 1.16.5 } else if (version.compareTo(ClientProfile.Version.MC118) <= 0) { // 1.13 - 1.16.5
jvmArgs.add("-XX:+UseG1GC"); jvmArgs.add("-XX:+UseG1GC");
jvmArgs.add("-XX:+UnlockExperimentalVMOptions"); jvmArgs.add("-XX:+UnlockExperimentalVMOptions");
} else { // 1.18+ } else { // 1.18+
@ -236,7 +236,7 @@ public static MakeProfileOption[] getMakeProfileOptionsFromDir(Path dir, ClientP
if (Files.exists(dir.resolve("libraries/forge/launchwrapper-1.12-launcherfixed.jar.jar")) || Files.exists(dir.resolve("libraries/net/minecraft/launchwrapper"))) { if (Files.exists(dir.resolve("libraries/forge/launchwrapper-1.12-launcherfixed.jar.jar")) || Files.exists(dir.resolve("libraries/net/minecraft/launchwrapper"))) {
options.add(new MakeProfileOptionLaunchWrapper()); options.add(new MakeProfileOptionLaunchWrapper());
} }
if(globalAssets) { if (globalAssets) {
options.add(new MakeProfileOptionGlobalAssets()); options.add(new MakeProfileOptionGlobalAssets());
} }
return options.toArray(new MakeProfileOption[0]); return options.toArray(new MakeProfileOption[0]);

View file

@ -150,6 +150,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
} else { } else {
try (Reader reader = IOHelper.newReader(configPath)) { try (Reader reader = IOHelper.newReader(configPath)) {
targetConfig = Launcher.gsonManager.configGson.fromJson(reader, clazz); 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<>(); if (entity.propertyMap == null) entity.propertyMap = new HashMap<>();

View file

@ -27,13 +27,7 @@
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*; import java.util.*;
public class AuthManager { public class AuthManager {
@ -60,9 +54,6 @@ public String newCheckServerToken(String serverName, String authId) {
.compact(); .compact();
} }
public record CheckServerTokenInfo(String serverName, String authId) {
}
public CheckServerTokenInfo parseCheckServerToken(String token) { public CheckServerTokenInfo parseCheckServerToken(String token) {
try { try {
var jwt = checkServerTokenParser.parseClaimsJws(token).getBody(); 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 * Create AuthContext
* *
@ -154,7 +122,7 @@ public AuthReport auth(AuthResponse.AuthContext context, AuthRequest.AuthPasswor
String login = context.login; String login = context.login;
try { try {
AuthReport result = provider.authorize(login, context, password, context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context)); AuthReport result = provider.authorize(login, context, password, context.authType == AuthResponse.ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context));
if(result == null || result.session == null || result.session.getUser() == null) { if (result == null || result.session == null || result.session.getUser() == null) {
logger.error("AuthCoreProvider {} method 'authorize' return null", context.pair.name); logger.error("AuthCoreProvider {} method 'authorize' return null", context.pair.name);
throw new AuthException("Internal Auth Error"); throw new AuthException("Internal Auth Error");
} }
@ -175,7 +143,7 @@ public AuthReport auth(AuthResponse.AuthContext context, AuthRequest.AuthPasswor
* Writing authorization information to the Client object * Writing authorization information to the Client object
*/ */
public void internalAuth(Client client, AuthResponse.ConnectTypes authType, AuthProviderPair pair, String username, UUID uuid, ClientPermissions permissions, boolean oauth) { public void internalAuth(Client client, AuthResponse.ConnectTypes authType, AuthProviderPair pair, String username, UUID uuid, ClientPermissions permissions, boolean oauth) {
if(!oauth) { if (!oauth) {
throw new UnsupportedOperationException("Unsupported legacy session system"); throw new UnsupportedOperationException("Unsupported legacy session system");
} }
client.isAuth = true; client.isAuth = true;
@ -265,7 +233,7 @@ public PlayerProfile getPlayerProfile(AuthProviderPair pair, UUID uuid, ClientPr
public PlayerProfile getPlayerProfile(AuthProviderPair pair, User user) { public PlayerProfile getPlayerProfile(AuthProviderPair pair, User user) {
Map<String, String> properties; Map<String, String> properties;
if(user instanceof UserSupportProperties userSupportProperties) { if (user instanceof UserSupportProperties userSupportProperties) {
properties = userSupportProperties.getProperties(); properties = userSupportProperties.getProperties();
} else { } else {
properties = new HashMap<>(); properties = new HashMap<>();
@ -325,6 +293,32 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au
return password; 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 static class CheckServerReport {
public UUID uuid; public UUID uuid;
public User user; public User user;

View file

@ -66,7 +66,7 @@ public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpec
IOHelper.write(rsaPrivateKeyPath, rsaPrivateKey.getEncoded()); IOHelper.write(rsaPrivateKeyPath, rsaPrivateKey.getEncoded());
} }
Path legacySaltPath = keyDirectory.resolve("legacySalt"); Path legacySaltPath = keyDirectory.resolve("legacySalt");
if(IOHelper.isFile(legacySaltPath)) { if (IOHelper.isFile(legacySaltPath)) {
legacySalt = new String(IOHelper.read(legacySaltPath), StandardCharsets.UTF_8); legacySalt = new String(IOHelper.read(legacySaltPath), StandardCharsets.UTF_8);
} else { } else {
legacySalt = SecurityHelper.randomStringToken(); legacySalt = SecurityHelper.randomStringToken();

View file

@ -1,6 +1,5 @@
package pro.gravit.launchserver.manangers.hook; package pro.gravit.launchserver.manangers.hook;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;

View file

@ -65,12 +65,12 @@ public <T> void setProperty(String name, T object) {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public<T> T getStaticProperty(String name) { public <T> T getStaticProperty(String name) {
if (staticProperties == null) staticProperties = new HashMap<>(); if (staticProperties == null) staticProperties = new HashMap<>();
return (T) staticProperties.get(name); return (T) staticProperties.get(name);
} }
public<T> void setStaticProperty(String name, T value) { public <T> void setStaticProperty(String name, T value) {
if (staticProperties == null) staticProperties = new HashMap<>(); if (staticProperties == null) staticProperties = new HashMap<>();
staticProperties.put(name, value); staticProperties.put(name, value);
} }

View file

@ -93,6 +93,22 @@ public static void registerResponses() {
providers.register("getPublicKey", GetPublicKeyResponse.class); 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) { public void forEachActiveChannels(BiConsumer<Channel, WebSocketFrameHandler> callback) {
for (Channel channel : channels) { for (Channel channel : channels) {
if (channel == null || channel.pipeline() == null) continue; if (channel == null || channel.pipeline() == null) continue;
@ -176,25 +192,9 @@ public void registerClient(Channel channel) {
channels.add(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) { public void sendObject(ChannelHandlerContext ctx, Object obj) {
String msg = gson.toJson(obj, WebSocketEvent.class); String msg = gson.toJson(obj, WebSocketEvent.class);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to {}: {}", getIPFromContext(ctx), msg); logger.trace("Send to {}: {}", getIPFromContext(ctx), msg);
} }
ctx.writeAndFlush(new TextWebSocketFrame(msg), ctx.voidPromise()); ctx.writeAndFlush(new TextWebSocketFrame(msg), ctx.voidPromise());
@ -202,7 +202,7 @@ public void sendObject(ChannelHandlerContext ctx, Object obj) {
public void sendObject(ChannelHandlerContext ctx, Object obj, Type type) { public void sendObject(ChannelHandlerContext ctx, Object obj, Type type) {
String msg = gson.toJson(obj, type); String msg = gson.toJson(obj, type);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to {}: {}", getIPFromContext(ctx), msg); logger.trace("Send to {}: {}", getIPFromContext(ctx), msg);
} }
ctx.writeAndFlush(new TextWebSocketFrame(msg), ctx.voidPromise()); ctx.writeAndFlush(new TextWebSocketFrame(msg), ctx.voidPromise());
@ -210,7 +210,7 @@ public void sendObject(ChannelHandlerContext ctx, Object obj, Type type) {
public void sendObject(Channel channel, Object obj) { public void sendObject(Channel channel, Object obj) {
String msg = gson.toJson(obj, WebSocketEvent.class); String msg = gson.toJson(obj, WebSocketEvent.class);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to channel {}: {}", getIPFromChannel(channel), msg); logger.trace("Send to channel {}: {}", getIPFromChannel(channel), msg);
} }
channel.writeAndFlush(new TextWebSocketFrame(msg), channel.voidPromise()); channel.writeAndFlush(new TextWebSocketFrame(msg), channel.voidPromise());
@ -218,7 +218,7 @@ public void sendObject(Channel channel, Object obj) {
public void sendObject(Channel channel, Object obj, Type type) { public void sendObject(Channel channel, Object obj, Type type) {
String msg = gson.toJson(obj, type); String msg = gson.toJson(obj, type);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to channel {}: {}", getIPFromChannel(channel), msg); logger.trace("Send to channel {}: {}", getIPFromChannel(channel), msg);
} }
channel.writeAndFlush(new TextWebSocketFrame(msg), channel.voidPromise()); channel.writeAndFlush(new TextWebSocketFrame(msg), channel.voidPromise());
@ -226,7 +226,7 @@ public void sendObject(Channel channel, Object obj, Type type) {
public void sendObjectAll(Object obj) { public void sendObjectAll(Object obj) {
String msg = gson.toJson(obj, WebSocketEvent.class); String msg = gson.toJson(obj, WebSocketEvent.class);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to all: {}", msg); logger.trace("Send to all: {}", msg);
} }
for (Channel ch : channels) { for (Channel ch : channels) {
@ -236,7 +236,7 @@ public void sendObjectAll(Object obj) {
public void sendObjectAll(Object obj, Type type) { public void sendObjectAll(Object obj, Type type) {
String msg = gson.toJson(obj, type); String msg = gson.toJson(obj, type);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to all: {}", msg); logger.trace("Send to all: {}", msg);
} }
for (Channel ch : channels) { for (Channel ch : channels) {
@ -252,7 +252,7 @@ public void sendObjectToUUID(UUID userUuid, Object obj, Type type) {
Client client = wsHandler.getClient(); Client client = wsHandler.getClient();
if (client == null || !userUuid.equals(client.uuid)) continue; if (client == null || !userUuid.equals(client.uuid)) continue;
String msg = gson.toJson(obj, type); String msg = gson.toJson(obj, type);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send to {}({}): {}", getIPFromChannel(ch), userUuid, msg); logger.trace("Send to {}({}): {}", getIPFromChannel(ch), userUuid, msg);
} }
ch.writeAndFlush(new TextWebSocketFrame(msg), ch.voidPromise()); ch.writeAndFlush(new TextWebSocketFrame(msg), ch.voidPromise());
@ -320,7 +320,7 @@ public boolean kickByIP(String ip, boolean isClose) {
public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj) { public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj) {
String msg = gson.toJson(obj, WebSocketEvent.class); String msg = gson.toJson(obj, WebSocketEvent.class);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send and close {}: {}", getIPFromContext(ctx), msg); logger.trace("Send and close {}: {}", getIPFromContext(ctx), msg);
} }
ctx.writeAndFlush(new TextWebSocketFrame(msg)).addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(new TextWebSocketFrame(msg)).addListener(ChannelFutureListener.CLOSE);
@ -328,7 +328,7 @@ public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj) {
public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj, Type type) { public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj, Type type) {
String msg = gson.toJson(obj, type); String msg = gson.toJson(obj, type);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send and close {}: {}", getIPFromContext(ctx), msg); logger.trace("Send and close {}: {}", getIPFromContext(ctx), msg);
} }
ctx.writeAndFlush(new TextWebSocketFrame(msg)).addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(new TextWebSocketFrame(msg)).addListener(ChannelFutureListener.CLOSE);
@ -337,7 +337,7 @@ public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj, Type type)
@Deprecated @Deprecated
public void sendEvent(EventResult obj) { public void sendEvent(EventResult obj) {
String msg = gson.toJson(obj, WebSocketEvent.class); String msg = gson.toJson(obj, WebSocketEvent.class);
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Send event: {}", msg); logger.trace("Send event: {}", msg);
} }
channels.writeAndFlush(new TextWebSocketFrame(msg), ChannelMatchers.all(), true); channels.writeAndFlush(new TextWebSocketFrame(msg), ChannelMatchers.all(), true);

View file

@ -64,7 +64,7 @@ protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
logger.error("WebSocket frame handler hook error", ex); logger.error("WebSocket frame handler hook error", ex);
} }
if (frame instanceof TextWebSocketFrame) { if (frame instanceof TextWebSocketFrame) {
if(logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Message from {}: {}", context.ip == null ? IOHelper.getIP(ctx.channel().remoteAddress()) : context.ip, ((TextWebSocketFrame) frame).text()); logger.trace("Message from {}: {}", context.ip == null ? IOHelper.getIP(ctx.channel().remoteAddress()) : context.ip, ((TextWebSocketFrame) frame).text());
} }
try { try {

View file

@ -1,7 +1,6 @@
package pro.gravit.launchserver.socket.response.auth; package pro.gravit.launchserver.socket.response.auth;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.events.request.AdditionalDataRequestEvent; import pro.gravit.launcher.events.request.AdditionalDataRequestEvent;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.User; import pro.gravit.launchserver.auth.core.User;

View file

@ -12,8 +12,6 @@
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.utils.HookException; import pro.gravit.utils.HookException;
import java.util.UUID;
public class AuthResponse extends SimpleResponse { public class AuthResponse extends SimpleResponse {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public String login; public String login;
@ -46,6 +44,7 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
server.authHookManager.preHook.hook(context, clientData); server.authHookManager.preHook.hook(context, clientData);
context.report = server.authManager.auth(context, password); context.report = server.authManager.auth(context, password);
server.authHookManager.postHook.hook(context, clientData); 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()) { if (context.report.isUsingOAuth()) {
result.oauth = new AuthRequestEvent.OAuthRequestEvent(context.report.oauthAccessToken(), context.report.oauthRefreshToken(), context.report.oauthExpire()); result.oauth = new AuthRequestEvent.OAuthRequestEvent(context.report.oauthAccessToken(), context.report.oauthRefreshToken(), context.report.oauthExpire());
} }

View file

@ -31,7 +31,7 @@ public void execute(ChannelHandlerContext ctx, Client pClient) {
try { try {
server.authHookManager.checkServerHook.hook(this, pClient); server.authHookManager.checkServerHook.hook(this, pClient);
AuthManager.CheckServerReport report = server.authManager.checkServer(pClient, username, serverID); AuthManager.CheckServerReport report = server.authManager.checkServer(pClient, username, serverID);
if(report == null) { if (report == null) {
sendError("User not verified"); sendError("User not verified");
return; return;
} }

View file

@ -22,7 +22,7 @@ public static void exit(LaunchServer server, WebSocketFrameHandler wsHandler, Ch
Client chClient = wsHandler.getClient(); Client chClient = wsHandler.getClient();
Client newCusClient = new Client(); Client newCusClient = new Client();
newCusClient.checkSign = chClient.checkSign; newCusClient.checkSign = chClient.checkSign;
if(chClient.staticProperties != null) { if (chClient.staticProperties != null) {
newCusClient.staticProperties = new HashMap<>(chClient.staticProperties); newCusClient.staticProperties = new HashMap<>(chClient.staticProperties);
} }
wsHandler.setClient(newCusClient); wsHandler.setClient(newCusClient);

View file

@ -2,7 +2,6 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.FetchClientProfileKeyRequestEvent; 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.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys; import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
@ -22,7 +21,7 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception {
} }
UserSession session = client.sessionObject; UserSession session = client.sessionObject;
UserSessionSupportKeys.ClientProfileKeys keys; UserSessionSupportKeys.ClientProfileKeys keys;
if(session instanceof UserSessionSupportKeys support) { if (session instanceof UserSessionSupportKeys support) {
keys = support.getClientProfileKeys(); keys = support.getClientProfileKeys();
} else { } else {
keys = server.authManager.createClientProfileKeys(client.uuid); keys = server.authManager.createClientProfileKeys(client.uuid);

View file

@ -3,6 +3,7 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent;
import pro.gravit.launchserver.auth.AuthProviderPair; 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.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -19,8 +20,14 @@ public String getType() {
public void execute(ChannelHandlerContext ctx, Client client) { public void execute(ChannelHandlerContext ctx, Client client) {
List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>(); List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>();
for (AuthProviderPair pair : server.config.auth.values()) { 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, 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)); sendResult(new GetAvailabilityAuthRequestEvent(list));
} }

View file

@ -3,6 +3,7 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.ProfilesRequestEvent; import pro.gravit.launcher.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -12,18 +13,7 @@
import java.util.Set; import java.util.Set;
public class ProfilesResponse extends SimpleResponse { public class ProfilesResponse extends SimpleResponse {
@Override public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
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;
}
List<ClientProfile> profileList; List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = server.getProfiles(); Set<ClientProfile> serverProfiles = server.getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) { if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
@ -36,6 +26,20 @@ public void execute(ChannelHandlerContext ctx, Client client) {
} else { } else {
profileList = List.copyOf(serverProfiles); 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)));
} }
} }

View file

@ -0,0 +1,4 @@
{
"features": [],
"info": []
}

View file

@ -81,7 +81,7 @@ task dumpLibs(type: Copy) {
pom { pom {
name = 'GravitLauncher Client API' name = 'GravitLauncher Client API'
description = 'GravitLauncher Client Module API' description = 'GravitLauncher Client Module API'
url = 'https://launcher.gravit.pro' url = 'https://gravitlauncher.com'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -103,7 +103,7 @@ task dumpLibs(type: Copy) {
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/' url = 'https://gravitlauncher.com/'
} }
} }
} }

View file

@ -7,7 +7,6 @@
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestService; import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.WebSocketEvent; import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.launcher.request.websockets.ClientWebSocketService;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
public class BasicLauncherEventHandler implements RequestService.EventHandler { public class BasicLauncherEventHandler implements RequestService.EventHandler {

View file

@ -140,7 +140,7 @@ public static void main(String[] arguments) throws IOException, InterruptedExcep
if (context.memoryLimit != 0) { if (context.memoryLimit != 0) {
args.add(String.format("-Xmx%dM", context.memoryLimit)); args.add(String.format("-Xmx%dM", context.memoryLimit));
} }
if(customJvmOptions != null) { if (customJvmOptions != null) {
args.addAll(customJvmOptions); args.addAll(customJvmOptions);
} }
args.add("-cp"); args.add("-cp");

View file

@ -179,7 +179,7 @@ public static void applyBasicOfflineProcessors(OfflineRequestService service) {
service.registerRequestProcessor(GetAvailabilityAuthRequest.class, (r) -> { service.registerRequestProcessor(GetAvailabilityAuthRequest.class, (r) -> {
List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details = new ArrayList<>(); List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details = new ArrayList<>();
details.add(new AuthLoginOnlyDetails()); 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<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>(1);
list.add(authAvailability); list.add(authAvailability);
return new GetAvailabilityAuthRequestEvent(list); return new GetAvailabilityAuthRequestEvent(list);
@ -246,14 +246,14 @@ public void start(String... args) throws Throwable {
try { try {
service = StdWebSocketService.initWebSockets(address).get(); service = StdWebSocketService.initWebSockets(address).get();
} catch (Throwable e) { } catch (Throwable e) {
if(LogHelper.isDebugEnabled()) { if (LogHelper.isDebugEnabled()) {
LogHelper.error(e); LogHelper.error(e);
} }
LogHelper.warning("Launcher in offline mode"); LogHelper.warning("Launcher in offline mode");
service = initOffline(); service = initOffline();
} }
Request.setRequestService(service); Request.setRequestService(service);
if(service instanceof StdWebSocketService) { if (service instanceof StdWebSocketService) {
((StdWebSocketService) service).reconnectCallback = () -> ((StdWebSocketService) service).reconnectCallback = () ->
{ {
LogHelper.debug("WebSocket connect closed. Try reconnect"); LogHelper.debug("WebSocket connect closed. Try reconnect");

View file

@ -1,8 +1,6 @@
package pro.gravit.launcher.api; package pro.gravit.launcher.api;
import pro.gravit.launcher.LauncherEngine; import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.utils.helper.LogHelper;
public class SystemService { public class SystemService {
private SystemService() { private SystemService() {

View file

@ -121,7 +121,7 @@ public static void main(String[] args) throws Throwable {
List<URL> classpathURLs = classpath.stream().map(IOHelper::toURL).collect(Collectors.toList()); List<URL> classpathURLs = classpath.stream().map(IOHelper::toURL).collect(Collectors.toList());
// Start client with WatchService monitoring // Start client with WatchService monitoring
RequestService service; RequestService service;
if(params.offlineMode) { if (params.offlineMode) {
service = initOffline(LauncherEngine.modulesManager, params); service = initOffline(LauncherEngine.modulesManager, params);
Request.setRequestService(service); Request.setRequestService(service);
} else { } else {
@ -231,13 +231,13 @@ public static RequestService initOffline(LauncherModulesManager modulesManager,
public static void applyClientOfflineProcessors(OfflineRequestService service, ClientLauncherProcess.ClientParams params) { public static void applyClientOfflineProcessors(OfflineRequestService service, ClientLauncherProcess.ClientParams params) {
service.registerRequestProcessor(ProfileByUsernameRequest.class, (r) -> { service.registerRequestProcessor(ProfileByUsernameRequest.class, (r) -> {
if(params.playerProfile.username.equals(r.username)) { if (params.playerProfile.username.equals(r.username)) {
return new ProfileByUsernameRequestEvent(params.playerProfile); return new ProfileByUsernameRequestEvent(params.playerProfile);
} }
throw new RequestException("User not found"); throw new RequestException("User not found");
}); });
service.registerRequestProcessor(ProfileByUUIDRequest.class, (r) -> { service.registerRequestProcessor(ProfileByUUIDRequest.class, (r) -> {
if(params.playerProfile.uuid.equals(r.uuid)) { if (params.playerProfile.uuid.equals(r.uuid)) {
return new ProfileByUUIDRequestEvent(params.playerProfile); return new ProfileByUUIDRequestEvent(params.playerProfile);
} }
throw new RequestException("User not found"); throw new RequestException("User not found");

View file

@ -3,7 +3,6 @@
import pro.gravit.launcher.Launcher; import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig; import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.LauncherEngine; 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.ClientProcessBuilderCreateEvent;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderLaunchedEvent; import pro.gravit.launcher.client.events.client.ClientProcessBuilderLaunchedEvent;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderParamsWrittedEvent; import pro.gravit.launcher.client.events.client.ClientProcessBuilderParamsWrittedEvent;
@ -28,7 +27,6 @@
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -79,7 +77,7 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString(); this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
this.params.assetDir = assetDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString();
Path nativesPath = workDir.resolve("natives").resolve(JVMHelper.OS_TYPE.name).resolve(javaVersion.arch.name); Path nativesPath = workDir.resolve("natives").resolve(JVMHelper.OS_TYPE.name).resolve(javaVersion.arch.name);
if(!Files.isDirectory(nativesPath)) { if (!Files.isDirectory(nativesPath)) {
nativesPath = workDir.resolve("natives"); nativesPath = workDir.resolve("natives");
} }
this.params.nativesDir = nativesPath.toString(); this.params.nativesDir = nativesPath.toString();
@ -158,7 +156,7 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
.map(Path::toString) .map(Path::toString)
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
if(Launcher.getConfig().environment != LauncherConfig.LauncherEnvironment.PROD) { if (Launcher.getConfig().environment != LauncherConfig.LauncherEnvironment.PROD) {
processArgs.add(JVMHelper.jvmProperty(LogHelper.DEV_PROPERTY, String.valueOf(LogHelper.isDevEnabled()))); processArgs.add(JVMHelper.jvmProperty(LogHelper.DEV_PROPERTY, String.valueOf(LogHelper.isDevEnabled())));
processArgs.add(JVMHelper.jvmProperty(LogHelper.DEBUG_PROPERTY, String.valueOf(LogHelper.isDebugEnabled()))); processArgs.add(JVMHelper.jvmProperty(LogHelper.DEBUG_PROPERTY, String.valueOf(LogHelper.isDebugEnabled())));
processArgs.add(JVMHelper.jvmProperty(LogHelper.STACKTRACE_PROPERTY, String.valueOf(LogHelper.isStacktraceEnabled()))); processArgs.add(JVMHelper.jvmProperty(LogHelper.STACKTRACE_PROPERTY, String.valueOf(LogHelper.isStacktraceEnabled())));
@ -211,7 +209,7 @@ private void applyJava9Params(List<String> processArgs) {
if (modulesAdd.length() > 0) modulesAdd.append(","); if (modulesAdd.length() > 0) modulesAdd.append(",");
modulesAdd.append(moduleName); modulesAdd.append(moduleName);
} }
for(String modulePath : jvmModulesPaths) { for (String modulePath : jvmModulesPaths) {
if (modulesPath.length() > 0) modulesPath.append(File.pathSeparator); if (modulesPath.length() > 0) modulesPath.append(File.pathSeparator);
modulesPath.append(modulePath); modulesPath.append(modulePath);
} }

View file

@ -1,5 +1,6 @@
package pro.gravit.launcher.client; package pro.gravit.launcher.client;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
@ -162,9 +163,13 @@ private Result modernPing(HInput input, HOutput output) throws IOException {
} }
// Parse JSON response // Parse JSON response
JsonObject object = JsonParser.parseString(response).getAsJsonObject(); JsonElement element = JsonParser.parseString(response);
if(object.has("error")) { if(element.isJsonPrimitive()) {
throw new IOException(object.get("error").getAsString()); throw new IOException(element.getAsString());
}
JsonObject object = element.getAsJsonObject();
if (object.has("error")) {
throw new IOException(object.get("error").getAsString()); // May be not needed?
} }
JsonObject playersObject = object.get("players").getAsJsonObject(); JsonObject playersObject = object.get("players").getAsJsonObject();
int online = playersObject.get("online").getAsInt(); int online = playersObject.get("online").getAsInt();

View file

@ -24,10 +24,10 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
for(LauncherModule module : LauncherEngine.modulesManager.getModules()) { for (LauncherModule module : LauncherEngine.modulesManager.getModules()) {
LauncherModuleInfo info = module.getModuleInfo(); LauncherModuleInfo info = module.getModuleInfo();
LauncherTrustManager.CheckClassResult checkStatus = module.getCheckResult(); LauncherTrustManager.CheckClassResult checkStatus = module.getCheckResult();
if(!ConsoleManager.isConsoleUnlock) { if (!ConsoleManager.isConsoleUnlock) {
LogHelper.info("[MODULE] %s v: %s", info.name, info.version.getVersionString()); LogHelper.info("[MODULE] %s v: %s", info.name, info.version.getVersionString());
} else { } else {
LogHelper.info("[MODULE] %s v: %s p: %d deps: %s sig: %s", info.name, info.version.getVersionString(), info.priority, Arrays.toString(info.dependencies), checkStatus == null ? "null" : checkStatus.type); LogHelper.info("[MODULE] %s v: %s p: %d deps: %s sig: %s", info.name, info.version.getVersionString(), info.priority, Arrays.toString(info.dependencies), checkStatus == null ? "null" : checkStatus.type);

View file

@ -57,7 +57,7 @@ public static void main(String[] args) throws Throwable {
ConsoleManager.initConsole(); ConsoleManager.initConsole();
LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase()); LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase());
RequestService service; RequestService service;
if(offlineMode) { if (offlineMode) {
OfflineRequestService offlineRequestService = new OfflineRequestService(); OfflineRequestService offlineRequestService = new OfflineRequestService();
LauncherEngine.applyBasicOfflineProcessors(offlineRequestService); LauncherEngine.applyBasicOfflineProcessors(offlineRequestService);
OfflineModeEvent event = new OfflineModeEvent(offlineRequestService); OfflineModeEvent event = new OfflineModeEvent(offlineRequestService);

View file

@ -18,7 +18,7 @@ public static void checkCertificatesSuccess(X509Certificate[] certs) throws Exce
} }
public static String findLibrary(ClassLoader classLoader, String library) { public static String findLibrary(ClassLoader classLoader, String library) {
if(classLoader instanceof ClientClassLoader) { if (classLoader instanceof ClientClassLoader) {
ClientClassLoader clientClassLoader = (ClientClassLoader) classLoader; ClientClassLoader clientClassLoader = (ClientClassLoader) classLoader;
return clientClassLoader.findLibrary(library); return clientClassLoader.findLibrary(library);
} }

View file

@ -1,10 +1,8 @@
package pro.gravit.launcher.utils; package pro.gravit.launcher.utils;
import pro.gravit.launcher.AsyncDownloader; import pro.gravit.launcher.AsyncDownloader;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherEngine; import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.LauncherInject; import pro.gravit.launcher.LauncherInject;
import pro.gravit.launcher.events.request.LauncherRequestEvent;
import pro.gravit.launcher.request.update.LauncherRequest; import pro.gravit.launcher.request.update.LauncherRequest;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
@ -13,12 +11,10 @@
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -30,14 +26,16 @@
public class LauncherUpdater { public class LauncherUpdater {
@LauncherInject("launcher.certificatePinning") @LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning; private static boolean isCertificatePinning;
public static void nothing() { public static void nothing() {
} }
private static Path getLauncherPath() { private static Path getLauncherPath() {
Path pathToCore = IOHelper.getCodeSource(IOHelper.class); Path pathToCore = IOHelper.getCodeSource(IOHelper.class);
Path pathToApi = IOHelper.getCodeSource(LauncherRequest.class); Path pathToApi = IOHelper.getCodeSource(LauncherRequest.class);
Path pathToSelf = IOHelper.getCodeSource(LauncherUpdater.class); Path pathToSelf = IOHelper.getCodeSource(LauncherUpdater.class);
if(pathToCore.equals(pathToApi) && pathToCore.equals(pathToSelf)) { if (pathToCore.equals(pathToApi) && pathToCore.equals(pathToSelf)) {
return pathToCore; return pathToCore;
} else { } else {
throw new SecurityException("Found split-jar launcher"); throw new SecurityException("Found split-jar launcher");

View file

@ -1,11 +1,8 @@
package pro.gravit.launcher.utils; package pro.gravit.launcher.utils;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
public final class NativeJVMHalt { public final class NativeJVMHalt {
public final int haltCode; public final int haltCode;
@ -30,7 +27,7 @@ public static void haltA(int code) {
exitMethod.invoke(null, code); exitMethod.invoke(null, code);
} catch (Throwable e) { } catch (Throwable e) {
th[1] = e; th[1] = e;
if(LogHelper.isDevEnabled()) { if (LogHelper.isDevEnabled()) {
LogHelper.error(e); LogHelper.error(e);
} }
} }

View file

@ -59,7 +59,7 @@ task javadocJar(type: Jar) {
pom { pom {
name = 'GravitLauncher WebSocket API' name = 'GravitLauncher WebSocket API'
description = 'GravitLauncher WebSocket Module API' description = 'GravitLauncher WebSocket Module API'
url = 'https://launcher.gravit.pro' url = 'https://gravitlauncher.com'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -81,7 +81,7 @@ task javadocJar(type: Jar) {
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/' url = 'https://gravitlauncher.com/'
} }
} }
} }

View file

@ -1,8 +1,5 @@
package pro.gravit.launcher; package pro.gravit.launcher;
import pro.gravit.launcher.serialize.HInput;
import java.io.IOException;
import java.util.*; import java.util.*;
public class ClientPermissions { public class ClientPermissions {
@ -71,7 +68,7 @@ public void addPerm(String perm) {
perms = new ArrayList<>(1); perms = new ArrayList<>(1);
} }
perms.add(perm); perms.add(perm);
if(available == null) { if (available == null) {
available = new ArrayList<>(1); available = new ArrayList<>(1);
} }
available.add(new PermissionPattern(perm)); available.add(new PermissionPattern(perm));
@ -81,7 +78,7 @@ public void removePerm(String action) {
if (perms == null) { if (perms == null) {
return; return;
} }
if(available == null) { if (available == null) {
return; return;
} }
perms.remove(action); perms.remove(action);
@ -110,11 +107,11 @@ public static class PermissionPattern {
public PermissionPattern(String pattern) { public PermissionPattern(String pattern) {
List<String> prepare = new ArrayList<>(); List<String> prepare = new ArrayList<>();
for(int i=0;true;) { for (int i = 0; true; ) {
int pos = pattern.indexOf("*", i); int pos = pattern.indexOf("*", i);
if(pos >= 0) { if (pos >= 0) {
prepare.add(pattern.substring(i, pos)); prepare.add(pattern.substring(i, pos));
i = pos+1; i = pos + 1;
} else { } else {
prepare.add(pattern.substring(i)); prepare.add(pattern.substring(i));
break; break;
@ -129,23 +126,23 @@ public int getPriority() {
} }
public boolean match(String str) { public boolean match(String str) {
if(parts.length == 0) { if (parts.length == 0) {
return true; return true;
} }
if(parts.length == 1) { if (parts.length == 1) {
return parts[0].equals(str); return parts[0].equals(str);
} }
int offset = 0; int offset = 0;
if(!str.startsWith(parts[0])) { if (!str.startsWith(parts[0])) {
return false; return false;
} }
if(!str.endsWith(parts[parts.length-1])) { if (!str.endsWith(parts[parts.length - 1])) {
return false; return false;
} }
for(int i=1;i<parts.length-1;++i) { for (int i = 1; i < parts.length - 1; ++i) {
int pos = str.indexOf(parts[i], offset); int pos = str.indexOf(parts[i], offset);
if(pos >= 0) { if (pos >= 0) {
offset = pos+1; offset = pos + 1;
} else { } else {
return false; return false;
} }

View file

@ -46,10 +46,27 @@ public static class AuthAvailability {
@LauncherNetworkAPI @LauncherNetworkAPI
public String displayName; 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.name = name;
this.displayName = displayName; this.displayName = displayName;
this.visible = visible;
this.details = details; 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;
}
} }
} }

View file

@ -484,7 +484,8 @@ public enum Version {
MC1182("1.18.2", 758), MC1182("1.18.2", 758),
MC119("1.19", 759), MC119("1.19", 759),
MC1191("1.19.1", 760), 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; private static final Map<String, Version> VERSIONS;
static { static {

View file

@ -1,7 +1,6 @@
package pro.gravit.launcher.profiles; package pro.gravit.launcher.profiles;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -30,10 +29,10 @@ public PlayerProfile(UUID uuid, String username, Texture skin, Texture cloak, Ma
this.skin = skin; this.skin = skin;
this.cloak = cloak; this.cloak = cloak;
this.assets = new HashMap<>(); this.assets = new HashMap<>();
if(skin != null) { if (skin != null) {
this.assets.put("SKIN", skin); this.assets.put("SKIN", skin);
} }
if(cloak != null) { if (cloak != null) {
this.assets.put("CAPE", cloak); this.assets.put("CAPE", cloak);
} }
this.properties = properties; this.properties = properties;

View file

@ -27,6 +27,21 @@ public OptionalView(OptionalView view) {
this.all = view.all; 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") @SuppressWarnings("unchecked")
public <T extends OptionalAction> Set<T> getActionsByClass(Class<T> clazz) { public <T extends OptionalAction> Set<T> getActionsByClass(Class<T> clazz) {
Set<T> results = new HashSet<>(); Set<T> results = new HashSet<>();
@ -42,6 +57,19 @@ public <T extends OptionalAction> Set<T> getActionsByClass(Class<T> clazz) {
return results; 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() { public Set<OptionalAction> getEnabledActions() {
Set<OptionalAction> results = new HashSet<>(); Set<OptionalAction> results = new HashSet<>();
for (OptionalFile e : enabled) { for (OptionalFile e : enabled) {

View file

@ -5,6 +5,7 @@
public class ArchTrigger extends OptionalTrigger { public class ArchTrigger extends OptionalTrigger {
public JVMHelper.ARCH arch; public JVMHelper.ARCH arch;
@Override @Override
protected boolean isTriggered(OptionalFile optional, OptionalTriggerContext context) { protected boolean isTriggered(OptionalFile optional, OptionalTriggerContext context) {
return context.getJavaVersion().arch == arch; return context.getJavaVersion().arch == arch;

View file

@ -28,17 +28,17 @@ public abstract class Request<R extends WebSocketEvent> implements WebSocketRequ
public final UUID requestUUID = UUID.randomUUID(); public final UUID requestUUID = UUID.randomUUID();
private transient final AtomicBoolean started = new AtomicBoolean(false); private transient final AtomicBoolean started = new AtomicBoolean(false);
public static void setRequestService(RequestService service) {
requestService = service;
if(service instanceof StdWebSocketService) {
Request.service = (StdWebSocketService) service;
}
}
public static RequestService getRequestService() { public static RequestService getRequestService() {
return requestService; return requestService;
} }
public static void setRequestService(RequestService service) {
requestService = service;
if (service instanceof StdWebSocketService) {
Request.service = (StdWebSocketService) service;
}
}
public static boolean isAvailable() { public static boolean isAvailable() {
return requestService != null; return requestService != null;
} }
@ -121,22 +121,10 @@ public static RequestRestoreReport reconnect() throws Exception {
return restore(); 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 { public static RequestRestoreReport restore() throws Exception {
boolean refreshed = false; boolean refreshed = false;
RestoreRequest request; RestoreRequest request;
if(oauth != null) { if (oauth != null) {
if (isTokenExpired() || oauth.accessToken == null) { if (isTokenExpired() || oauth.accessToken == null) {
RefreshTokenRequest refreshRequest = new RefreshTokenRequest(authId, oauth.refreshToken); RefreshTokenRequest refreshRequest = new RefreshTokenRequest(authId, oauth.refreshToken);
RefreshTokenRequestEvent event = refreshRequest.request(); RefreshTokenRequestEvent event = refreshRequest.request();
@ -197,7 +185,7 @@ public void removeOAuthChangeHandler(BiConsumer<String, AuthRequestEvent.OAuthRe
public R request() throws Exception { public R request() throws Exception {
if (!started.compareAndSet(false, true)) if (!started.compareAndSet(false, true))
throw new IllegalStateException("Request already started"); throw new IllegalStateException("Request already started");
if(!isAvailable()) { if (!isAvailable()) {
throw new RequestException("RequestService not initialized"); throw new RequestException("RequestService not initialized");
} }
return requestDo(requestService); return requestDo(requestService);
@ -224,4 +212,16 @@ public interface ExtendedTokenCallback {
String tryGetNewToken(String name); 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;
}
}
} }

View file

@ -6,8 +6,11 @@
public interface RequestService { public interface RequestService {
<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException; <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException;
void registerEventHandler(EventHandler handler); void registerEventHandler(EventHandler handler);
void unregisterEventHandler(EventHandler handler); void unregisterEventHandler(EventHandler handler);
default <T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException { default <T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException {
try { try {
return request(request).get(); return request(request).get();

View file

@ -5,21 +5,13 @@
import pro.gravit.launcher.events.request.LauncherRequestEvent; import pro.gravit.launcher.events.request.LauncherRequestEvent;
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestService; import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.request.websockets.WebSocketRequest; import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
import java.io.IOException; 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.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public final class LauncherRequest extends Request<LauncherRequestEvent> implements WebSocketRequest { public final class LauncherRequest extends Request<LauncherRequestEvent> implements WebSocketRequest {
public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class); public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class);

View file

@ -5,7 +5,6 @@
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.WebSocketRequest; import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper;
import java.io.IOException; import java.io.IOException;

View file

@ -4,7 +4,6 @@
import pro.gravit.launcher.events.request.ProfileByUsernameRequestEvent; import pro.gravit.launcher.events.request.ProfileByUsernameRequestEvent;
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.WebSocketRequest; import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.VerifyHelper;
public final class ProfileByUsernameRequest extends Request<ProfileByUsernameRequestEvent> implements WebSocketRequest { public final class ProfileByUsernameRequest extends Request<ProfileByUsernameRequestEvent> implements WebSocketRequest {
@LauncherNetworkAPI @LauncherNetworkAPI

View file

@ -24,7 +24,6 @@
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -112,10 +111,10 @@ public void openAsync(Runnable onConnect, Consumer<Throwable> onFail) {
uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE, 12800000), this); uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE, 12800000), this);
ChannelFuture future = bootstrap.connect(uri.getHost(), port); ChannelFuture future = bootstrap.connect(uri.getHost(), port);
future.addListener((l) -> { future.addListener((l) -> {
if(l.isSuccess()) { if (l.isSuccess()) {
ch = future.channel(); ch = future.channel();
webSocketClientHandler.handshakeFuture().addListener((e) -> { webSocketClientHandler.handshakeFuture().addListener((e) -> {
if(e.isSuccess()) { if (e.isSuccess()) {
onConnect.run(); onConnect.run();
} else { } else {
onFail.accept(webSocketClientHandler.handshakeFuture().cause()); onFail.accept(webSocketClientHandler.handshakeFuture().cause());

View file

@ -82,7 +82,7 @@ public void registerRequests() {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void registerResults() { public void registerResults() {
if(!resultsRegistered) { if (!resultsRegistered) {
results.register("auth", AuthRequestEvent.class); results.register("auth", AuthRequestEvent.class);
results.register("checkServer", CheckServerRequestEvent.class); results.register("checkServer", CheckServerRequestEvent.class);
results.register("joinServer", JoinServerRequestEvent.class); results.register("joinServer", JoinServerRequestEvent.class);

View file

@ -16,26 +16,27 @@
public class OfflineRequestService implements RequestService { public class OfflineRequestService implements RequestService {
private final HashSet<EventHandler> eventHandlers = new HashSet<>(); private final HashSet<EventHandler> eventHandlers = new HashSet<>();
private final Map<Class<?>, RequestProcessor<?, ?>> processors = new ConcurrentHashMap<>(); private final Map<Class<?>, RequestProcessor<?, ?>> processors = new ConcurrentHashMap<>();
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException { public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
RequestProcessor<T, Request<T>> processor = (RequestProcessor<T, Request<T>>) processors.get(request.getClass()); RequestProcessor<T, Request<T>> processor = (RequestProcessor<T, Request<T>>) processors.get(request.getClass());
CompletableFuture<T> future = new CompletableFuture<>(); CompletableFuture<T> future = new CompletableFuture<>();
if(processor == null) { if (processor == null) {
future.completeExceptionally(new RequestException(String.format("Offline mode not support '%s'", request.getType()))); future.completeExceptionally(new RequestException(String.format("Offline mode not support '%s'", request.getType())));
return future; return future;
} }
if(LogHelper.isDevEnabled()) { if (LogHelper.isDevEnabled()) {
LogHelper.dev("Request %s: %s", request.getType(), Launcher.gsonManager.gson.toJson(request)); LogHelper.dev("Request %s: %s", request.getType(), Launcher.gsonManager.gson.toJson(request));
} }
try { try {
T event = processor.process(request); T event = processor.process(request);
if(LogHelper.isDevEnabled()) { if (LogHelper.isDevEnabled()) {
LogHelper.dev("Response %s: %s", event.getType(), Launcher.gsonManager.gson.toJson(event)); LogHelper.dev("Response %s: %s", event.getType(), Launcher.gsonManager.gson.toJson(event));
} }
future.complete(event); future.complete(event);
} catch (Throwable e) { } catch (Throwable e) {
if(e instanceof RequestException) { if (e instanceof RequestException) {
future.completeExceptionally(e); future.completeExceptionally(e);
} else { } else {
future.completeExceptionally(new RequestException(e)); future.completeExceptionally(new RequestException(e));
@ -59,11 +60,11 @@ public boolean isClosed() {
return false; return false;
} }
public<T extends WebSocketEvent, V extends WebSocketRequest> void registerRequestProcessor(Class<V> requestClazz, RequestProcessor<T, V> function) { public <T extends WebSocketEvent, V extends WebSocketRequest> void registerRequestProcessor(Class<V> requestClazz, RequestProcessor<T, V> function) {
processors.put(requestClazz, function); processors.put(requestClazz, function);
} }
public<T extends WebSocketEvent> void unregisterRequestProcessor(Class<Request<T>> requestClazz) { public <T extends WebSocketEvent> void unregisterRequestProcessor(Class<Request<T>> requestClazz) {
processors.remove(requestClazz); processors.remove(requestClazz);
} }

View file

@ -55,7 +55,6 @@ public static CompletableFuture<StdWebSocketService> initWebSockets(String addre
} }
@Deprecated @Deprecated
public void registerEventHandler(ClientWebSocketService.EventHandler handler) { public void registerEventHandler(ClientWebSocketService.EventHandler handler) {
legacyEventHandlers.add(handler); legacyEventHandlers.add(handler);

View file

@ -65,7 +65,7 @@ task javadocJar(type: Jar) {
pom { pom {
name = 'GravitLauncher Core Utils' name = 'GravitLauncher Core Utils'
description = 'GravitLauncher Core Utils' description = 'GravitLauncher Core Utils'
url = 'https://launcher.gravit.pro' url = 'https://gravitlauncher.com'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -87,7 +87,7 @@ task javadocJar(type: Jar) {
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/' url = 'https://gravitlauncher.com/'
} }
} }
} }

View file

@ -69,6 +69,10 @@ public long size() {
return size; return size;
} }
public byte[] getDigest() {
return digest;
}
@Override @Override
public void write(HOutput output) throws IOException { public void write(HOutput output) throws IOException {
output.writeVarLong(size); output.writeVarLong(size);

View file

@ -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 { public static void downloadZip(URL url, Path dir) throws IOException {
try (ZipInputStream input = IOHelper.newZipInput(url)) { try (ZipInputStream input = IOHelper.newZipInput(url)) {
Files.createDirectory(dir);
for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) { for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
if (entry.isDirectory()) { if (entry.isDirectory()) {
Files.createDirectory(dir.resolve(IOHelper.toPath(entry.getName()))); Files.createDirectory(dir.resolve(IOHelper.toPath(entry.getName())));

View file

@ -6,7 +6,7 @@ public final class Version implements Comparable<Version> {
public static final int MAJOR = 5; public static final int MAJOR = 5;
public static final int MINOR = 3; 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 int BUILD = 1;
public static final Version.Type RELEASE = Type.STABLE; public static final Version.Type RELEASE = Type.STABLE;
public final int major; public final int major;

View file

@ -4,8 +4,6 @@
import pro.gravit.utils.command.CommandException; import pro.gravit.utils.command.CommandException;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;

View file

@ -39,6 +39,7 @@ public final class IOHelper {
public static final FileSystem FS = FileSystems.getDefault(); public static final FileSystem FS = FileSystems.getDefault();
// Platform-dependent // Platform-dependent
public static final String PLATFORM_SEPARATOR = FS.getSeparator(); 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 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 JVM_DIR = Paths.get(System.getProperty("java.home"));
public static final Path HOME_DIR = Paths.get(System.getProperty("user.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); private static final Set<FileVisitOption> WALK_OPTIONS = Collections.singleton(FileVisitOption.FOLLOW_LINKS);
// Other constants // Other constants
private static final Pattern CROSS_SEPARATOR_PATTERN = Pattern.compile(CROSS_SEPARATOR, Pattern.LITERAL); 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() { private IOHelper() {
} }

View file

@ -20,12 +20,10 @@ public final class JVMHelper {
public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN = public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN =
ManagementFactory.getOperatingSystemMXBean(); ManagementFactory.getOperatingSystemMXBean();
public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName()); 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 @Deprecated
public static final int OS_BITS = getCorrectOSArch(); 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 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 int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
@ -46,21 +44,11 @@ public final class JVMHelper {
private 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) { 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("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; if (arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) return ARCH.X86;
if(arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64; if (arch.startsWith("armv8") || arch.startsWith("aarch64")) return ARCH.ARM64;
if(arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32; if (arch.startsWith("arm") || arch.startsWith("aarch32")) return ARCH.ARM32;
throw new InternalError(String.format("Unsupported arch '%s'", arch)); throw new InternalError(String.format("Unsupported arch '%s'", arch));
} }
@ -110,19 +98,16 @@ public static Class<?> firstClass(String... names) throws ClassNotFoundException
throw new ClassNotFoundException(Arrays.toString(names)); throw new ClassNotFoundException(Arrays.toString(names));
} }
public static void fullGC() { public static void fullGC() {
RUNTIME.gc(); RUNTIME.gc();
RUNTIME.runFinalization(); RUNTIME.runFinalization();
LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20); LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20);
} }
public static String[] getClassPath() { public static String[] getClassPath() {
return System.getProperty("java.class.path").split(File.pathSeparator); return System.getProperty("java.class.path").split(File.pathSeparator);
} }
public static URL[] getClassPathURL() { public static URL[] getClassPathURL() {
String[] cp = System.getProperty("java.class.path").split(File.pathSeparator); String[] cp = System.getProperty("java.class.path").split(File.pathSeparator);
URL[] list = new URL[cp.length]; URL[] list = new URL[cp.length];
@ -164,35 +149,29 @@ private static int getCorrectOSArch() {
return System.getProperty("os.arch").contains("64") ? 64 : 32; return System.getProperty("os.arch").contains("64") ? 64 : 32;
} }
public static String getEnvPropertyCaseSensitive(String name) { public static String getEnvPropertyCaseSensitive(String name) {
return System.getenv().get(name); return System.getenv().get(name);
} }
@Deprecated @Deprecated
public static boolean isJVMMatchesSystemArch() { public static boolean isJVMMatchesSystemArch() {
return JVM_BITS == OS_BITS; return JVM_BITS == OS_BITS;
} }
public static String jvmProperty(String name, String value) { public static String jvmProperty(String name, String value) {
return String.format("-D%s=%s", name, value); return String.format("-D%s=%s", name, value);
} }
public static String systemToJvmProperty(String name) { public static String systemToJvmProperty(String name) {
return String.format("-D%s=%s", name, System.getProperties().getProperty(name)); return String.format("-D%s=%s", name, System.getProperties().getProperty(name));
} }
public static void addSystemPropertyToArgs(Collection<String> args, String name) { public static void addSystemPropertyToArgs(Collection<String> args, String name) {
String property = System.getProperty(name); String property = System.getProperty(name);
if (property != null) if (property != null)
args.add(String.format("-D%s=%s", name, property)); args.add(String.format("-D%s=%s", name, property));
} }
public static void verifySystemProperties(Class<?> mainClass, boolean requireSystem) { public static void verifySystemProperties(Class<?> mainClass, boolean requireSystem) {
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
// Verify class loader // Verify class loader
@ -204,6 +183,17 @@ public static void verifySystemProperties(Class<?> mainClass, boolean requireSys
LogHelper.debug("Verifying JVM architecture"); 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 { public enum OS {
MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx"); MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx");

View file

@ -12,6 +12,7 @@
public class JavaHelper { public class JavaHelper {
private static List<JavaVersion> javaVersionsCache; private static List<JavaVersion> javaVersionsCache;
public static Path tryGetOpenJFXPath(Path jvmDir) { public static Path tryGetOpenJFXPath(Path jvmDir) {
String dirName = jvmDir.getFileName().toString(); String dirName = jvmDir.getFileName().toString();
Path parent = jvmDir.getParent(); Path parent = jvmDir.getParent();
@ -57,7 +58,7 @@ public static boolean tryAddModule(List<Path> paths, String moduleName, StringBu
} }
public synchronized static List<JavaVersion> findJava() { public synchronized static List<JavaVersion> findJava() {
if(javaVersionsCache != null) { if (javaVersionsCache != null) {
return javaVersionsCache; return javaVersionsCache;
} }
List<String> javaPaths = new ArrayList<>(4); List<String> javaPaths = new ArrayList<>(4);
@ -106,11 +107,11 @@ public synchronized static List<JavaVersion> findJava() {
} }
private static JavaVersion tryFindJavaByPath(Path path) { private static JavaVersion tryFindJavaByPath(Path path) {
if(javaVersionsCache == null) { if (javaVersionsCache == null) {
return null; return null;
} }
for(JavaVersion version : javaVersionsCache) { for (JavaVersion version : javaVersionsCache) {
if(version.jvmDir.equals(path)) { if (version.jvmDir.equals(path)) {
return version; return version;
} }
} }
@ -156,8 +157,8 @@ public static JavaVersionAndBuild getJavaVersion(String version) {
result.build = Integer.parseInt(version.substring(dot + 1)); result.build = Integer.parseInt(version.substring(dot + 1));
} else { } else {
try { try {
if(version.endsWith("-ea")) { if (version.endsWith("-ea")) {
version = version.substring(0, version.length()-3); version = version.substring(0, version.length() - 3);
} }
result.version = Integer.parseInt(version); result.version = Integer.parseInt(version);
result.build = 0; result.build = 0;
@ -232,7 +233,7 @@ private static boolean isCurrentJavaSupportJavaFX() {
public static JavaVersion getByPath(Path jvmDir) throws IOException { public static JavaVersion getByPath(Path jvmDir) throws IOException {
{ {
JavaVersion version = JavaHelper.tryFindJavaByPath(jvmDir); JavaVersion version = JavaHelper.tryFindJavaByPath(jvmDir);
if(version != null) { if (version != null) {
return version; return version;
} }
} }
@ -249,7 +250,7 @@ public static JavaVersion getByPath(Path jvmDir) throws IOException {
arch = null; arch = null;
} }
} else { } 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); JavaVersion resultJavaVersion = new JavaVersion(jvmDir, versionAndBuild.version, versionAndBuild.build, arch, false);
if (versionAndBuild.version <= 8) { if (versionAndBuild.version <= 8) {
@ -264,8 +265,10 @@ public static JavaVersion getByPath(Path jvmDir) throws IOException {
public static boolean isExistExtJavaLibrary(Path jvmDir, String name) { public static boolean isExistExtJavaLibrary(Path jvmDir, String name) {
Path jrePath = jvmDir.resolve("lib").resolve("ext").resolve(name.concat(".jar")); 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")); 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);
} }
} }
} }

View file

@ -4,7 +4,7 @@
* [See license](LICENSE) * [See license](LICENSE)
* [See code of conduct](CODE_OF_CONDUCT.md) * [See code of conduct](CODE_OF_CONDUCT.md)
* [WIKI](https://launcher.gravit.pro) * [WIKI](https://gravitlauncher.com)
* Get it (requires cURL): * Get it (requires cURL):
```sh ```sh

View file

@ -64,7 +64,7 @@ pack project(':LauncherAPI')
shadowJar { shadowJar {
duplicatesStrategy = 'EXCLUDE' duplicatesStrategy = 'EXCLUDE'
classifier = null archiveClassifier = null
relocate 'io.netty', 'pro.gravit.repackage.io.netty' relocate 'io.netty', 'pro.gravit.repackage.io.netty'
configurations = [project.configurations.pack] configurations = [project.configurations.pack]
exclude 'module-info.class' exclude 'module-info.class'
@ -82,7 +82,7 @@ pack project(':LauncherAPI')
pom { pom {
name = 'GravitLauncher ServerWrapper API' name = 'GravitLauncher ServerWrapper API'
description = 'GravitLauncher ServerWrapper Module API' description = 'GravitLauncher ServerWrapper Module API'
url = 'https://launcher.gravit.pro' url = 'https://gravitlauncher.com'
licenses { licenses {
license { license {
name = 'GNU General Public License, Version 3.0' name = 'GNU General Public License, Version 3.0'
@ -105,7 +105,7 @@ pack project(':LauncherAPI')
scm { scm {
connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git' connection = 'scm:git:https://github.com/GravitLauncher/Launcher.git'
developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git' developerConnection = 'scm:git:ssh://git@github.com:GravitLauncher/Launcher.git'
url = 'https://launcher.gravit.pro/' url = 'https://gravitlauncher.com/'
} }
} }
} }

View file

@ -180,9 +180,9 @@ public void run(String... args) throws Throwable {
break; break;
} }
LogHelper.info("Start Minecraft Server"); 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 { try {
launch.run(config, real_args); launch.run(classname, config, real_args);
} catch (Throwable e) { } catch (Throwable e) {
LogHelper.error(e); LogHelper.error(e);
System.exit(-1); System.exit(-1);

View file

@ -20,6 +20,7 @@ public class InstallAuthlib {
modifierMap.put("META-INF/libraries.list", new LibrariesLstModifier()); modifierMap.put("META-INF/libraries.list", new LibrariesLstModifier());
modifierMap.put("patch.properties", new PatchPropertiesModifier()); modifierMap.put("patch.properties", new PatchPropertiesModifier());
modifierMap.put("META-INF/download-context", new DownloadContextModifier()); modifierMap.put("META-INF/download-context", new DownloadContextModifier());
modifierMap.put("META-INF/patches.list", new PatchesLstModifier());
} }
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
boolean deleteAuthlibAfterInstall = false; boolean deleteAuthlibAfterInstall = false;

View file

@ -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);
}
}

View file

@ -13,10 +13,10 @@
public class ClasspathLaunch implements Launch { public class ClasspathLaunch implements Launch {
@Override @Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod") @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); URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
ClassLoader ucl = new PublicURLClassLoader(urls); 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)); MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args); mainMethod.invoke(args);
} }

View file

@ -3,5 +3,5 @@
import pro.gravit.launcher.server.ServerWrapper; import pro.gravit.launcher.server.ServerWrapper;
public interface Launch { public interface Launch {
void run(ServerWrapper.Config config, String[] args) throws Throwable; void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable;
} }

View file

@ -4,7 +4,7 @@
public class ModuleLaunch implements Launch { public class ModuleLaunch implements Launch {
@Override @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"); throw new UnsupportedOperationException("Module system not supported");
} }
} }

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