Merge branch 'release/5.3.2'

This commit is contained in:
Gravita 2022-12-09 14:02:19 +07:00
commit dd5ce00c3f
88 changed files with 1029 additions and 1172 deletions

View file

@ -17,25 +17,6 @@ public class HttpRequester {
public HttpRequester() {
}
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if(statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
if(type == Void.class) {
return new HttpHelper.HttpOptional<>(null, null, statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public <T> SimpleErrorHandler<T> makeEH(Class<T> clazz) {
return new SimpleErrorHandler<>(clazz);
}
@ -78,6 +59,26 @@ public <T> HttpHelper.HttpOptional<T, SimpleError> send(HttpRequest request, Cla
return HttpHelper.send(httpClient, request, makeEH(clazz));
}
public static class SimpleErrorHandler<T> implements HttpHelper.HttpJsonErrorHandler<T, SimpleError> {
private final Type type;
private SimpleErrorHandler(Type type) {
this.type = type;
}
@Override
public HttpHelper.HttpOptional<T, SimpleError> applyJson(JsonElement response, int statusCode) {
if (statusCode < 200 || statusCode >= 300) {
return new HttpHelper.HttpOptional<>(null, Launcher.gsonManager.gson.fromJson(response, SimpleError.class), statusCode);
}
if (type == Void.class) {
return new HttpHelper.HttpOptional<>(null, null, statusCode);
}
return new HttpHelper.HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public static class SimpleError {
public String error;
public int code;

View file

@ -8,7 +8,6 @@
import pro.gravit.launcher.managers.ConfigManager;
import pro.gravit.launcher.modules.events.ClosePhase;
import pro.gravit.launcher.profiles.ClientProfile;
import pro.gravit.launcher.request.Request;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.binary.EXEL4JLauncherBinary;
@ -68,6 +67,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
* The path to the folder with compile-only libraries for the launcher
*/
public final Path launcherLibrariesCompile;
public final Path launcherPack;
/**
* The path to the folder with updates/webroot
*/
@ -138,6 +138,10 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.service = Executors.newScheduledThreadPool(config.netty.performance.schedulerThread);
launcherLibraries = directories.launcherLibrariesDir;
launcherLibrariesCompile = directories.launcherLibrariesCompileDir;
launcherPack = directories.launcherPackDir;
if(!Files.isDirectory(launcherPack)) {
Files.createDirectories(launcherPack);
}
config.setLaunchServer(this);
@ -487,11 +491,12 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
public static class LaunchServerDirectories {
public static final String UPDATES_NAME = "updates", PROFILES_NAME = "profiles",
TRUSTSTORE_NAME = "truststore", LAUNCHERLIBRARIES_NAME = "launcher-libraries",
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", KEY_NAME = ".keys";
LAUNCHERLIBRARIESCOMPILE_NAME = "launcher-libraries-compile", LAUNCHERPACK_NAME = "launcher-pack", KEY_NAME = ".keys";
public Path updatesDir;
public Path profilesDir;
public Path launcherLibrariesDir;
public Path launcherLibrariesCompileDir;
public Path launcherPackDir;
public Path keyDirectory;
public Path dir;
public Path trustStore;
@ -504,6 +509,8 @@ public void collect() {
if (launcherLibrariesDir == null) launcherLibrariesDir = getPath(LAUNCHERLIBRARIES_NAME);
if (launcherLibrariesCompileDir == null)
launcherLibrariesCompileDir = getPath(LAUNCHERLIBRARIESCOMPILE_NAME);
if(launcherPackDir == null)
launcherPackDir = getPath(LAUNCHERPACK_NAME);
if (keyDirectory == null) keyDirectory = getPath(KEY_NAME);
if (tmpDir == null)
tmpDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve(String.format("launchserver-%s", SecurityHelper.randomStringToken()));

View file

@ -38,15 +38,6 @@ public static Set<String> getFeatures(Class<?> clazz) {
return list;
}
public void internalShowOAuthWarnMessage() {
if(!warnOAuthShow) {
if(!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
}
warnOAuthShow = true;
}
}
public static void getFeatures(Class<?> clazz, Set<String> list) {
Features features = clazz.getAnnotation(Features.class);
if (features != null) {
@ -64,6 +55,15 @@ public static void getFeatures(Class<?> clazz, Set<String> list) {
}
}
public void internalShowOAuthWarnMessage() {
if (!warnOAuthShow) {
if (!(core instanceof MySQLCoreProvider) && !(core instanceof PostgresSQLCoreProvider)) { // MySQL and PostgreSQL upgraded later
logger.warn("AuthCoreProvider {} ({}) not supported OAuth. Legacy session system may be removed in next release", name, core.getClass().getName());
}
warnOAuthShow = true;
}
}
public final <T> T isSupport(Class<T> clazz) {
if (core == null) return null;
T result = null;

View file

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

View file

@ -10,7 +10,7 @@
import java.sql.Connection;
import java.sql.SQLException;
public final class PostgreSQLSourceConfig implements AutoCloseable {
public final class PostgreSQLSourceConfig implements AutoCloseable, SQLSourceConfig {
public static final int TIMEOUT = VerifyHelper.verifyInt(
Integer.parseUnsignedInt(System.getProperty("launcher.postgresql.idleTimeout", Integer.toString(5000))),
VerifyHelper.POSITIVE, "launcher.postgresql.idleTimeout can't be <= 5000");

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,373 @@
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 = 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);
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 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();
}
private 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

@ -1,5 +1,6 @@
package pro.gravit.launchserver.auth.core;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
@ -28,7 +29,6 @@
public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSupportHardware, AuthSupportRemoteClientAccess {
private transient final Logger logger = LogManager.getLogger();
private transient HttpRequester requester;
public String bearerToken;
public String getUserByUsernameUrl;
public String getUserByLoginUrl;
@ -52,6 +52,7 @@ public class HttpAuthCoreProvider extends AuthCoreProvider implements AuthSuppor
public String unbanHardwareUrl;
public String apiUrl;
public List<String> apiFeatures;
private transient HttpRequester requester;
@Override
public User getUserByUsername(String username) {
@ -232,14 +233,15 @@ public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey)
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Iterable<User> getUsersByHardwareInfo(UserHardware hardware) {
if (getUsersByHardwareInfoUrl == null) {
return null;
}
try {
return requester.send(requester
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), List.class).getOrThrow();
return (List<User>) (List) requester.send(requester
.post(getUsersByHardwareInfoUrl, new HardwareRequest((HttpUserHardware) hardware), bearerToken), GetHardwareListResponse.class).getOrThrow().list;
} catch (IOException e) {
logger.error(e);
return null;
@ -280,14 +282,6 @@ public List<String> getClientApiFeatures() {
return apiFeatures;
}
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire,
HttpUserSession session) {
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
var result = requester.send(requester.post(updateServerIdUrl, new UpdateServerIdRequest(user.getUsername(), user.getUUID(), serverID),
@ -306,6 +300,36 @@ public boolean joinServer(Client client, String username, String accessToken, St
return result.isSuccessful();
}
@Override
public void init(LaunchServer server) {
requester = new HttpRequester();
if (getUserByUsernameUrl == null) {
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
}
if (getUserByUUIDUrl == null) {
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
}
if (authorizeUrl == null) {
throw new IllegalArgumentException("'authorizeUrl' can't be null");
}
if (checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
}
}
@Override
public void close() throws IOException {
}
public record HttpAuthReport(String minecraftAccessToken, String oauthAccessToken,
String oauthRefreshToken, long oauthExpire,
HttpUserSession session) {
public AuthManager.AuthReport toAuthReport() {
return new AuthManager.AuthReport(minecraftAccessToken, oauthAccessToken, oauthRefreshToken, oauthExpire, session);
}
}
public static class UpdateServerIdRequest {
public String username;
public UUID uuid;
@ -332,6 +356,10 @@ public static class GetAuthDetailsResponse {
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details;
}
public static class GetHardwareListResponse {
public List<HttpUser> list;
}
public static class JoinServerRequest {
public String username;
public String accessToken;
@ -344,28 +372,6 @@ public JoinServerRequest(String username, String accessToken, String serverId) {
}
}
@Override
public void init(LaunchServer server) {
requester = new HttpRequester();
if(getUserByUsernameUrl == null) {
throw new IllegalArgumentException("'getUserByUsernameUrl' can't be null");
}
if(getUserByUUIDUrl == null) {
throw new IllegalArgumentException("'getUserByUUIDUrl' can't be null");
}
if(authorizeUrl == null) {
throw new IllegalArgumentException("'authorizeUrl' can't be null");
}
if(checkServerUrl == null && joinServerUrl == null && updateServerIdUrl == null) {
throw new IllegalArgumentException("Please set 'checkServerUrl' and 'joinServerUrl' or 'updateServerIdUrl'");
}
}
@Override
public void close() throws IOException {
}
public static class AuthorizeRequest {
public String login;
public AuthResponse.AuthContext context;
@ -413,6 +419,106 @@ public HardwareRequest(byte[] key) {
}
public static class HttpUserSession implements UserSession {
private String id;
private HttpUser user;
private long expireIn;
public HttpUserSession() {
}
public HttpUserSession(String id, HttpUser user, long expireIn) {
this.id = id;
this.user = user;
this.expireIn = expireIn;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
@Override
public String toString() {
return "HttpUserSession{" +
"id='" + id + '\'' +
", user=" + user +
", expireIn=" + expireIn +
'}';
}
}
public static class HttpUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo) {
this.hardwareInfo = hardwareInfo;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.banned = banned;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(long id) {
this.id = id;
this.hardwareInfo = null;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "HttpUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
public class HttpUser implements User, UserSupportTextures, UserSupportProperties, UserSupportHardware {
private String username;
private UUID uuid;
@ -559,104 +665,4 @@ public UserHardware getHardware() {
return result;
}
}
public static class HttpUserSession implements UserSession {
private String id;
private HttpUser user;
private long expireIn;
public HttpUserSession() {
}
public HttpUserSession(String id, HttpUser user, long expireIn) {
this.id = id;
this.user = user;
this.expireIn = expireIn;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return expireIn;
}
@Override
public String toString() {
return "HttpUserSession{" +
"id='" + id + '\'' +
", user=" + user +
", expireIn=" + expireIn +
'}';
}
}
public static class HttpUserHardware implements UserHardware {
private final HardwareReportRequest.HardwareInfo hardwareInfo;
private final long id;
private byte[] publicKey;
private boolean banned;
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.id = id;
this.banned = banned;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo) {
this.hardwareInfo = hardwareInfo;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, boolean banned) {
this.hardwareInfo = hardwareInfo;
this.publicKey = publicKey;
this.banned = banned;
this.id = Long.MIN_VALUE;
}
public HttpUserHardware(long id) {
this.id = id;
this.hardwareInfo = null;
}
@Override
public HardwareReportRequest.HardwareInfo getHardwareInfo() {
return hardwareInfo;
}
@Override
public byte[] getPublicKey() {
return publicKey;
}
@Override
public String getId() {
return String.valueOf(id);
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public String toString() {
return "HttpUserHardware{" +
"hardwareInfo=" + hardwareInfo +
", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) +
", id=" + id +
", banned=" + banned +
'}';
}
}
}

View file

@ -13,10 +13,14 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class MemoryAuthCoreProvider extends AuthCoreProvider {
private transient final List<MemoryUser> memory = new ArrayList<>(16);
@Override
public User getUserByUsername(String username) {
synchronized (memory) {

View file

@ -1,59 +1,30 @@
package pro.gravit.launchserver.auth.core;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.*;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHardware {
private transient final Logger logger = LogManager.getLogger();
public class MySQLCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware {
public MySQLSourceConfig mySQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String hardwareIdColumn;
public String table;
public String tableHWID = "hwids";
public String tableHWIDLog = "hwidLog";
public PasswordVerifier passwordVerifier;
public double criticalCompareLevel = 1.0;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
private transient String sqlFindHardwareByPublicKey;
private transient String sqlFindHardwareByData;
private transient String sqlFindHardwareById;
@ -63,130 +34,16 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
private transient String sqlUpdateHardwareBanned;
private transient String sqlUpdateUsers;
private transient String sqlUsersByHwidId;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override
public User getUserByUsername(String username) {
try {
return query(queryByUsernameSQL, username);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return query(queryByUUIDSQL, uuid.toString());
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
try {
return query(queryByLoginSQL, login);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
try {
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
var user = (MySQLUser) getUserByUUID(info.uuid());
if(user == null) {
return null;
}
return new MySQLUserSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
String[] parts = refreshToken.split("\\.");
if(parts.length != 2) {
return null;
}
String username = parts[0];
String token = parts[1];
var user = (MySQLUser) getUserByUsername(username);
if(user == null || user.password == null) {
return null;
}
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
if(!token.equals(realToken)) {
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
MySQLUser mySQLUser = (MySQLUser) getUserByLogin(login);
if(mySQLUser == null) {
throw AuthException.wrongPassword();
}
if(context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if(plainPassword == null) {
throw AuthException.wrongPassword();
}
if(!passwordVerifier.check(mySQLUser.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
MySQLUserSession session = new MySQLUserSession(mySQLUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = mySQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(mySQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
}
public SQLSourceConfig getSQLConfig() {
return mySQLHolder;
}
@Override
public void init(LaunchServer server) {
this.server = server;
if (mySQLHolder == null) logger.error("mySQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
super.init(server);
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";
if (sqlFindHardwareByPublicKey == null)
sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID);
@ -206,43 +63,9 @@ public void init(LaunchServer server) {
sqlUpdateUsers = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, uuidColumn);
}
protected boolean updateAuth(User user, String accessToken) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
MySQLUser mySQLUser = (MySQLUser) user;
mySQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
MySQLUser mySQLUser = (MySQLUser) user;
mySQLUser.serverId = serverID;
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
public void close() throws IOException {
mySQLHolder.close();
}
private MySQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new MySQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions(), set.getLong(hardwareIdColumn)) : null;
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn)), set.getLong(hardwareIdColumn)) : null;
}
private MySQLUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {
@ -271,19 +94,6 @@ private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) th
s.executeUpdate();
}
private User query(String sql, String value) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) {
try (Connection connection = mySQLHolder.getConnection()) {
@ -371,8 +181,8 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa
@Override
public void connectUserAndHardware(UserSession userSession, UserHardware hardware) {
MySQLUserSession mySQLUserSession = (MySQLUserSession) userSession;
MySQLUser mySQLUser = mySQLUserSession.user;
SQLUserSession mySQLUserSession = (SQLUserSession) userSession;
MySQLUser mySQLUser = (MySQLUser) mySQLUserSession.getUser();
MySQLUserHardware mySQLUserHardware = (MySQLUserHardware) hardware;
if (mySQLUser.hwidId == mySQLUserHardware.id) return;
mySQLUser.hwidId = mySQLUserHardware.id;
@ -488,51 +298,15 @@ public String toString() {
}
}
public class MySQLUser implements User, UserSupportHardware {
protected UUID uuid;
protected String username;
protected String accessToken;
protected String serverId;
protected String password;
protected ClientPermissions permissions;
public class MySQLUser extends SQLUser implements UserSupportHardware {
protected long hwidId;
protected transient MySQLUserHardware hardware;
public MySQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
super(uuid, username, accessToken, serverId, password, permissions);
this.hwidId = hwidId;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public UserHardware getHardware() {
if (hardware != null) return hardware;
@ -551,29 +325,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;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.ClientPermissions;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthException;
import pro.gravit.launchserver.auth.MySQLSourceConfig;
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.helper.LegacySessionHelper;
import pro.gravit.launchserver.manangers.AuthManager;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.launchserver.auth.SQLSourceConfig;
import java.io.IOException;
import java.sql.*;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.UUID;
public class PostgresSQLCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
public class PostgresSQLCoreProvider extends AbstractSQLCoreProvider {
public PostgreSQLSourceConfig postgresSQLHolder;
public int expireSeconds = 3600;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String table;
public PasswordVerifier passwordVerifier;
public String customQueryByUUIDSQL;
public String customQueryByUsernameSQL;
public String customQueryByLoginSQL;
public String customUpdateAuthSQL;
public String customUpdateServerIdSQL;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String queryByLoginSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
private transient LaunchServer server;
@Override
public User getUserByUsername(String username) {
try {
return query(queryByUsernameSQL, username);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByUUID(UUID uuid) {
try {
return query(queryByUUIDSQL, uuid.toString());
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public User getUserByLogin(String login) {
try {
return query(queryByLoginSQL, login);
} catch (IOException e) {
logger.error("SQL error", e);
return null;
}
}
@Override
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
try {
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
var user = (PostgresSQLUser) getUserByUUID(info.uuid());
if(user == null) {
return null;
}
return new PostgresSQLCoreProvider.MySQLUserSession(user);
} catch (ExpiredJwtException e) {
throw new OAuthAccessTokenExpired();
} catch (JwtException e) {
return null;
}
}
@Override
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
String[] parts = refreshToken.split("\\.");
if(parts.length != 2) {
return null;
}
String username = parts[0];
String token = parts[1];
var user = (PostgresSQLUser) getUserByUsername(username);
if(user == null || user.password == null) {
return null;
}
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
if(!token.equals(realToken)) {
return null;
}
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new PostgresSQLCoreProvider.MySQLUserSession(user));
}
@Override
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) getUserByLogin(login);
if(postgresSQLUser == null) {
throw AuthException.wrongPassword();
}
if(context != null) {
AuthPlainPassword plainPassword = (AuthPlainPassword) password;
if(plainPassword == null) {
throw AuthException.wrongPassword();
}
if(!passwordVerifier.check(postgresSQLUser.password, plainPassword.password)) {
throw AuthException.wrongPassword();
}
}
MySQLUserSession session = new MySQLUserSession(postgresSQLUser);
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now(Clock.systemUTC()).plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
var refreshToken = postgresSQLUser.username.concat(".").concat(LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt));
if (minecraftAccess) {
String minecraftAccessToken = SecurityHelper.randomStringToken();
updateAuth(postgresSQLUser, minecraftAccessToken);
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
} else {
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
}
}
@Override
public void init(LaunchServer server) {
this.server = server;
if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null");
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null");
if (serverIDColumn == null) logger.error("serverIDColumn cannot be null");
if (table == null) logger.error("table cannot be null");
// Prepare SQL queries
String userInfoCols = String.format("%s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn);
queryByUUIDSQL = customQueryByUUIDSQL != null ? customQueryByUUIDSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", userInfoCols,
table, uuidColumn);
queryByUsernameSQL = customQueryByUsernameSQL != null ? customQueryByUsernameSQL : String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1",
userInfoCols, table, usernameColumn);
queryByLoginSQL = customQueryByLoginSQL != null ? customQueryByLoginSQL : queryByUsernameSQL;
updateAuthSQL = customUpdateAuthSQL != null ? customUpdateAuthSQL : String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=?",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = customUpdateServerIdSQL != null ? customUpdateServerIdSQL : String.format("UPDATE %s SET %s=? WHERE %s=?",
table, serverIDColumn, uuidColumn);
}
protected boolean updateAuth(User user, String accessToken) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
postgresSQLUser.accessToken = accessToken;
PreparedStatement s = c.prepareStatement(updateAuthSQL);
s.setString(1, accessToken);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
protected boolean updateServerID(User user, String serverID) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PostgresSQLUser postgresSQLUser = (PostgresSQLUser) user;
postgresSQLUser.serverId = serverID;
PreparedStatement s = c.prepareStatement(updateServerIDSQL);
s.setString(1, serverID);
s.setString(2, user.getUUID().toString());
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
return s.executeUpdate() > 0;
} catch (SQLException e) {
throw new IOException(e);
}
}
@Override
public void close() throws IOException {
postgresSQLHolder.close();
}
private PostgresSQLUser constructUser(ResultSet set) throws SQLException {
return set.next() ? new PostgresSQLUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn),
set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), new ClientPermissions()) : null;
}
private User query(String sql, String value) throws IOException {
try (Connection c = postgresSQLHolder.getConnection()) {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, value);
s.setQueryTimeout(MySQLSourceConfig.TIMEOUT);
try (ResultSet set = s.executeQuery()) {
return constructUser(set);
}
} catch (SQLException e) {
throw new IOException(e);
}
}
public static class PostgresSQLUser implements User {
protected UUID uuid;
protected String username;
protected String accessToken;
protected String serverId;
protected String password;
protected ClientPermissions permissions;
public PostgresSQLUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions) {
this.uuid = uuid;
this.username = username;
this.accessToken = accessToken;
this.serverId = serverId;
this.password = password;
this.permissions = permissions;
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public String getServerId() {
return serverId;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public ClientPermissions getPermissions() {
return permissions;
}
@Override
public String toString() {
return "PostgresSQLUser{" +
"uuid=" + uuid +
", username='" + username + '\'' +
", permissions=" + permissions +
'}';
}
}
public static class MySQLUserSession implements UserSession {
private final PostgresSQLUser user;
private final String id;
public MySQLUserSession(PostgresSQLUser user) {
this.user = user;
this.id = user.username;
}
@Override
public String getID() {
return id;
}
@Override
public User getUser() {
return user;
}
@Override
public long getExpireIn() {
return 0;
}
public SQLSourceConfig getSQLConfig() {
return postgresSQLHolder;
}
}

View file

@ -4,5 +4,6 @@
public interface AuthSupportRemoteClientAccess {
String getClientApiUrl();
List<String> getClientApiFeatures();
}

View file

@ -5,7 +5,9 @@
public interface UserSessionSupportKeys {
ClientProfileKeys getClientProfileKeys();
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt, long refreshedAfter) {
record ClientProfileKeys(PublicKey publicKey, PrivateKey privateKey, byte[] signature /* V2 */, long expiresAt,
long refreshedAfter) {
}
}

View file

@ -5,6 +5,7 @@
public class BCryptPasswordVerifier extends PasswordVerifier {
public int cost = 10;
@Override
public boolean check(String encryptedPassword, String password) {
return OpenBSDBCrypt.checkPassword(encryptedPassword, password.toCharArray());

View file

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

View file

@ -7,7 +7,6 @@
import pro.gravit.launcher.events.request.GetSecureLevelInfoRequestEvent;
import pro.gravit.launcher.events.request.HardwareReportRequestEvent;
import pro.gravit.launcher.events.request.VerifySecureLevelKeyRequestEvent;
import pro.gravit.launcher.request.secure.HardwareReportRequest;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware;

View file

@ -15,9 +15,10 @@
import java.util.UUID;
public class JsonTextureProvider extends TextureProvider {
public String url;
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {
}.getType();
private transient final Logger logger = LogManager.getLogger();
private transient static final Type MAP_TYPE = new TypeToken<Map<String, Texture>>() {}.getType();
public String url;
@Override
public void close() throws IOException {

View file

@ -33,17 +33,6 @@ public static void registerProviders() {
public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException;
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
@Deprecated
public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) {
@ -92,4 +81,15 @@ public Map<String, Texture> getAssets(UUID uuid, String username, String client)
return map;
}
@Deprecated
public static class SkinAndCloakTextures {
public final Texture skin;
public final Texture cloak;
public SkinAndCloakTextures(Texture skin, Texture cloak) {
this.skin = skin;
this.cloak = cloak;
}
}
}

View file

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

View file

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

View file

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

View file

@ -13,6 +13,8 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PrepareBuildTask implements LauncherBuildTask {
private final LaunchServer server;
@ -33,8 +35,19 @@ public String getName() {
public Path process(Path inputFile) throws IOException {
server.launcherBinary.coreLibs.clear();
server.launcherBinary.addonLibs.clear();
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), true);
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), true);
server.launcherBinary.files.clear();
IOHelper.walk(server.launcherLibraries, new ListFileVisitor(server.launcherBinary.coreLibs), false);
IOHelper.walk(server.launcherLibrariesCompile, new ListFileVisitor(server.launcherBinary.addonLibs), false);
try(Stream<Path> stream = Files.walk(server.launcherPack).filter((e) -> {
try {
return !Files.isDirectory(e) && !Files.isHidden(e);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
})) {
var map = stream.collect(Collectors.toMap(k -> server.launcherPack.relativize(k).toString(), (v) -> v));
server.launcherBinary.files.putAll(map);
}
UnpackHelper.unpack(IOHelper.getResourceURL("Launcher.jar"), result);
tryUnpack();
return result;

View file

@ -11,6 +11,7 @@
public class DebugCommand extends Command {
private transient Logger logger = LogManager.getLogger();
public DebugCommand(LaunchServer server) {
super(server);
}

View file

@ -10,9 +10,7 @@
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.Downloader;
import pro.gravit.utils.helper.IOHelper;
import proguard.OutputWriter;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
@ -22,11 +20,9 @@
import java.util.List;
public final class DownloadAssetCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
private transient final Logger logger = LogManager.getLogger();
public DownloadAssetCommand(LaunchServer server) {
super(server);

View file

@ -11,6 +11,7 @@
public class TokenCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public TokenCommand(LaunchServer server) {
super(server);
this.childCommands.put("info", new SubCommand("[token]", "print token info") {

View file

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

View file

@ -23,71 +23,11 @@
public final class HttpHelper {
private static transient final Logger logger = LogManager.getLogger();
private HttpHelper() {
throw new UnsupportedOperationException();
}
public static class HttpOptional<T,E> {
protected final T result;
protected final E error;
protected final int statusCode;
public HttpOptional(T result, E error, int statusCode) {
this.result = result;
this.error = error;
this.statusCode = statusCode;
}
public T result() {
return result;
}
public E error() {
return error;
}
public int statusCode() {
return statusCode;
}
public boolean isSuccessful() {
return statusCode >= 200 && statusCode < 300;
}
public T getOrThrow() throws RequestException {
if(isSuccessful()) {
return result;
} else {
throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString());
}
}
}
public interface HttpErrorHandler<T, E> {
HttpOptional<T,E> apply(HttpResponse<InputStream> response);
}
public interface HttpJsonErrorHandler<T, E> extends HttpErrorHandler<T,E> {
HttpOptional<T,E> applyJson(JsonElement response, int statusCode);
default HttpOptional<T,E> apply(HttpResponse<InputStream> response) {
try(Reader reader = new InputStreamReader(response.body())) {
var element = Launcher.gsonManager.gson.fromJson(reader, JsonElement.class);
return applyJson(element, response.statusCode());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> {
private final Class<T> type;
public BasicJsonHttpErrorHandler(Class<T> type) {
this.type = type;
}
@Override
public HttpOptional<T, Void> applyJson(JsonElement response, int statusCode) {
return new HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
public static <T, E> HttpOptional<T, E> send(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException {
try {
var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
@ -97,7 +37,6 @@ public static<T,E> HttpOptional<T,E> send(HttpClient client, HttpRequest request
}
}
public static <T, E> CompletableFuture<HttpOptional<T, E>> sendAsync(HttpClient client, HttpRequest request, HttpErrorHandler<T, E> handler) throws IOException {
return client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()).thenApply(handler::apply);
}
@ -120,6 +59,73 @@ public static<T> HttpRequest.BodyPublisher jsonBodyPublisher(T obj) {
return HttpRequest.BodyPublishers.ofString(Launcher.gsonManager.gson.toJson(obj));
}
public interface HttpErrorHandler<T, E> {
HttpOptional<T, E> apply(HttpResponse<InputStream> response);
}
public interface HttpJsonErrorHandler<T, E> extends HttpErrorHandler<T, E> {
HttpOptional<T, E> applyJson(JsonElement response, int statusCode);
default HttpOptional<T, E> apply(HttpResponse<InputStream> response) {
try (Reader reader = new InputStreamReader(response.body())) {
var element = Launcher.gsonManager.gson.fromJson(reader, JsonElement.class);
return applyJson(element, response.statusCode());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static class HttpOptional<T, E> {
protected final T result;
protected final E error;
protected final int statusCode;
public HttpOptional(T result, E error, int statusCode) {
this.result = result;
this.error = error;
this.statusCode = statusCode;
}
public T result() {
return result;
}
public E error() {
return error;
}
public int statusCode() {
return statusCode;
}
public boolean isSuccessful() {
return statusCode >= 200 && statusCode < 300;
}
public T getOrThrow() throws RequestException {
if (isSuccessful()) {
return result;
} else {
throw new RequestException(error == null ? String.format("statusCode %d", statusCode) : error.toString());
}
}
}
public static final class BasicJsonHttpErrorHandler<T> implements HttpJsonErrorHandler<T, Void> {
private final Class<T> type;
public BasicJsonHttpErrorHandler(Class<T> type) {
this.type = type;
}
@Override
public HttpOptional<T, Void> applyJson(JsonElement response, int statusCode) {
return new HttpOptional<>(Launcher.gsonManager.gson.fromJson(response, type), null, statusCode);
}
}
private static class JsonBodyHandler<T> implements HttpResponse.BodyHandler<T> {
private final HttpResponse.BodyHandler<InputStream> delegate;
private final Function<InputStream, T> func;

View file

@ -7,7 +7,6 @@
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.time.Clock;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;

View file

@ -27,13 +27,7 @@
import pro.gravit.utils.helper.SecurityHelper;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
public class AuthManager {
@ -60,9 +54,6 @@ public String newCheckServerToken(String serverName, String authId) {
.compact();
}
public record CheckServerTokenInfo(String serverName, String authId) {
}
public CheckServerTokenInfo parseCheckServerToken(String token) {
try {
var jwt = checkServerTokenParser.parseClaimsJws(token).getBody();
@ -72,29 +63,6 @@ public CheckServerTokenInfo parseCheckServerToken(String token) {
}
}
public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider {
private final LaunchServer server;
public CheckServerVerifier(LaunchServer server) {
this.server = server;
}
@Override
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
var info = server.authManager.parseCheckServerToken(extendedToken);
if(info == null) {
return false;
}
client.auth_id = info.authId;
client.auth = server.config.getAuthProviderPair(info.authId);
if(client.permissions == null) client.permissions = new ClientPermissions();
client.permissions.addPerm("launchserver.checkserver");
client.permissions.addPerm(String.format("launchserver.profile.%s.show", info.serverName));
client.setProperty("launchserver.serverName", info.serverName);
return true;
}
}
/**
* Create AuthContext
*
@ -325,6 +293,32 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au
return password;
}
public record CheckServerTokenInfo(String serverName, String authId) {
}
public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider {
private final LaunchServer server;
public CheckServerVerifier(LaunchServer server) {
this.server = server;
}
@Override
public boolean accept(Client client, AuthProviderPair pair, String extendedToken) {
var info = server.authManager.parseCheckServerToken(extendedToken);
if (info == null) {
return false;
}
client.auth_id = info.authId;
client.auth = server.config.getAuthProviderPair(info.authId);
if (client.permissions == null) client.permissions = new ClientPermissions();
client.permissions.addPerm("launchserver.checkserver");
client.permissions.addPerm(String.format("launchserver.profile.%s.show", info.serverName));
client.setProperty("launchserver.serverName", info.serverName);
return true;
}
}
public static class CheckServerReport {
public UUID uuid;
public User user;

View file

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

View file

@ -93,6 +93,22 @@ public static void registerResponses() {
providers.register("getPublicKey", GetPublicKeyResponse.class);
}
public static String getIPFromContext(ChannelHandlerContext ctx) {
var handler = ctx.pipeline().get(WebSocketFrameHandler.class);
if (handler == null || handler.context == null || handler.context.ip == null) {
return IOHelper.getIP(ctx.channel().remoteAddress());
}
return handler.context.ip;
}
public static String getIPFromChannel(Channel channel) {
var handler = channel.pipeline().get(WebSocketFrameHandler.class);
if (handler == null || handler.context == null || handler.context.ip == null) {
return IOHelper.getIP(channel.remoteAddress());
}
return handler.context.ip;
}
public void forEachActiveChannels(BiConsumer<Channel, WebSocketFrameHandler> callback) {
for (Channel channel : channels) {
if (channel == null || channel.pipeline() == null) continue;
@ -176,22 +192,6 @@ public void registerClient(Channel channel) {
channels.add(channel);
}
public static String getIPFromContext(ChannelHandlerContext ctx) {
var handler = ctx.pipeline().get(WebSocketFrameHandler.class);
if(handler == null || handler.context == null || handler.context.ip == null) {
return IOHelper.getIP(ctx.channel().remoteAddress());
}
return handler.context.ip;
}
public static String getIPFromChannel(Channel channel) {
var handler = channel.pipeline().get(WebSocketFrameHandler.class);
if(handler == null || handler.context == null || handler.context.ip == null) {
return IOHelper.getIP(channel.remoteAddress());
}
return handler.context.ip;
}
public void sendObject(ChannelHandlerContext ctx, Object obj) {
String msg = gson.toJson(obj, WebSocketEvent.class);
if (logger.isTraceEnabled()) {

View file

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

View file

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

View file

@ -2,7 +2,6 @@
import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.events.request.FetchClientProfileKeyRequestEvent;
import pro.gravit.launchserver.auth.core.User;
import pro.gravit.launchserver.auth.core.UserSession;
import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys;
import pro.gravit.launchserver.socket.Client;

View file

@ -13,20 +13,6 @@
import java.util.Set;
public class ProfilesResponse extends SimpleResponse {
@Override
public String getType() {
return "profiles";
}
@Override
public void execute(ChannelHandlerContext ctx, Client client) {
if (server.config.protectHandler instanceof ProfilesProtectHandler && !((ProfilesProtectHandler) server.config.protectHandler).canGetProfiles(client)) {
sendError("Access denied");
return;
}
sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
}
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = server.getProfiles();
@ -42,4 +28,18 @@ public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Cl
}
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

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

View file

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

View file

@ -3,7 +3,6 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherConfig;
import pro.gravit.launcher.LauncherEngine;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderCreateEvent;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderLaunchedEvent;
import pro.gravit.launcher.client.events.client.ClientProcessBuilderParamsWrittedEvent;
@ -28,7 +27,6 @@
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

View file

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

View file

@ -1,11 +1,8 @@
package pro.gravit.launcher.utils;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
public final class NativeJVMHalt {
public final int haltCode;

View file

@ -1,8 +1,5 @@
package pro.gravit.launcher;
import pro.gravit.launcher.serialize.HInput;
import java.io.IOException;
import java.util.*;
public class ClientPermissions {

View file

@ -484,7 +484,8 @@ public enum Version {
MC1182("1.18.2", 758),
MC119("1.19", 759),
MC1191("1.19.1", 760),
MC1192("1.19.2", 760);
MC1192("1.19.2", 760),
MC1193("1.19.3", 761);
private static final Map<String, Version> VERSIONS;
static {

View file

@ -1,7 +1,6 @@
package pro.gravit.launcher.profiles;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.VerifyHelper;
import java.util.HashMap;
import java.util.Map;

View file

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

View file

@ -28,6 +28,10 @@ public abstract class Request<R extends WebSocketEvent> implements WebSocketRequ
public final UUID requestUUID = UUID.randomUUID();
private transient final AtomicBoolean started = new AtomicBoolean(false);
public static RequestService getRequestService() {
return requestService;
}
public static void setRequestService(RequestService service) {
requestService = service;
if (service instanceof StdWebSocketService) {
@ -35,10 +39,6 @@ public static void setRequestService(RequestService service) {
}
}
public static RequestService getRequestService() {
return requestService;
}
public static boolean isAvailable() {
return requestService != null;
}
@ -121,18 +121,6 @@ public static RequestRestoreReport reconnect() throws Exception {
return restore();
}
public static class RequestRestoreReport {
public final boolean legacySession;
public final boolean refreshed;
public final List<String> invalidExtendedTokens;
public RequestRestoreReport(boolean legacySession, boolean refreshed, List<String> invalidExtendedTokens) {
this.legacySession = legacySession;
this.refreshed = refreshed;
this.invalidExtendedTokens = invalidExtendedTokens;
}
}
public static RequestRestoreReport restore() throws Exception {
boolean refreshed = false;
RestoreRequest request;
@ -224,4 +212,16 @@ public interface ExtendedTokenCallback {
String tryGetNewToken(String name);
}
public static class RequestRestoreReport {
public final boolean legacySession;
public final boolean refreshed;
public final List<String> invalidExtendedTokens;
public RequestRestoreReport(boolean legacySession, boolean refreshed, List<String> invalidExtendedTokens) {
this.legacySession = legacySession;
this.refreshed = refreshed;
this.invalidExtendedTokens = invalidExtendedTokens;
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,6 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

View file

@ -16,6 +16,7 @@
public class OfflineRequestService implements RequestService {
private final HashSet<EventHandler> eventHandlers = new HashSet<>();
private final Map<Class<?>, RequestProcessor<?, ?>> processors = new ConcurrentHashMap<>();
@Override
@SuppressWarnings("unchecked")
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,6 +12,7 @@
public class JavaHelper {
private static List<JavaVersion> javaVersionsCache;
public static Path tryGetOpenJFXPath(Path jvmDir) {
String dirName = jvmDir.getFileName().toString();
Path parent = jvmDir.getParent();

View file

@ -180,9 +180,9 @@ public void run(String... args) throws Throwable {
break;
}
LogHelper.info("Start Minecraft Server");
LogHelper.debug("Invoke main method %s with %s", config.mainclass, launch.getClass().getName());
LogHelper.debug("Invoke main method %s with %s", classname, launch.getClass().getName());
try {
launch.run(config, real_args);
launch.run(classname, config, real_args);
} catch (Throwable e) {
LogHelper.error(e);
System.exit(-1);

View file

@ -13,10 +13,10 @@
public class ClasspathLaunch implements Launch {
@Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
ClassLoader ucl = new PublicURLClassLoader(urls);
Class<?> mainClass = Class.forName(config.mainclass, true, ucl);
Class<?> mainClass = Class.forName(mainclass, true, ucl);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args);
}

View file

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

View file

@ -4,7 +4,7 @@
public class ModuleLaunch implements Launch {
@Override
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
throw new UnsupportedOperationException("Module system not supported");
}
}

View file

@ -9,8 +9,8 @@
public class SimpleLaunch implements Launch {
@Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
Class<?> mainClass = Class.forName(config.mainclass);
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
Class<?> mainClass = Class.forName(mainclass);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args);
}

View file

@ -13,12 +13,11 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
public class ModuleLaunch implements Launch {
@Override
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void run(ServerWrapper.Config config, String[] args) throws Throwable {
public void run(String mainclass, ServerWrapper.Config config, String[] args) throws Throwable {
URL[] urls = config.classpath.stream().map(Paths::get).map(IOHelper::toURL).toArray(URL[]::new);
ClassLoader ucl = new PublicURLClassLoader(urls);
// Create Module Layer
@ -55,7 +54,7 @@ public void run(ServerWrapper.Config config, String[] args) throws Throwable {
}
// Start main class
ClassLoader loader = mainModule.getClassLoader();
Class<?> mainClass = Class.forName(config.mainclass, true, loader);
Class<?> mainClass = Class.forName(mainclass, true, loader);
MethodHandle mainMethod = MethodHandles.lookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class));
mainMethod.invoke(args);
}

View file

@ -5,7 +5,7 @@
id 'org.openjfx.javafxplugin' version '0.0.10' apply false
}
group = 'pro.gravit.launcher'
version = '5.3.1'
version = '5.3.2'
apply from: 'props.gradle'

@ -1 +1 @@
Subproject commit 8677bc30c7e6c5f168131dbee63a1c10f2ea0394
Subproject commit aba8a880bd644c211f6f6d6fdceedd21adf66ca6