From cc931d0b1729858c15992b8189c2a1c911e3e2f1 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Mon, 3 Feb 2025 01:25:23 +0700 Subject: [PATCH] [FEATURE] Backport MariaDB auth provider --- LaunchServer/build.gradle | 1 + .../auth/MariaDBSourceConfig.java | 98 +++++ .../auth/core/AuthCoreProvider.java | 1 + .../auth/core/MariaDBCoreProvider.java | 354 ++++++++++++++++++ props.gradle | 1 + 5 files changed, 455 insertions(+) create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/MariaDBSourceConfig.java create mode 100644 LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MariaDBCoreProvider.java diff --git a/LaunchServer/build.gradle b/LaunchServer/build.gradle index b522f651..ececd2b0 100644 --- a/LaunchServer/build.gradle +++ b/LaunchServer/build.gradle @@ -87,6 +87,7 @@ pack project(':LauncherModernCore') bundle group: 'io.netty', name: 'netty-all', version: rootProject['verNetty'] bundle group: 'org.slf4j', name: 'slf4j-api', version: rootProject['verSlf4j'] bundle group: 'com.mysql', name: 'mysql-connector-j', version: rootProject['verMySQLConn'] + bundle group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: rootProject['verMariaDBConn'] bundle group: 'org.postgresql', name: 'postgresql', version: rootProject['verPostgreSQLConn'] bundle group: 'com.guardsquare', name: 'proguard-base', version: rootProject['verProguard'] bundle group: 'org.apache.logging.log4j', name: 'log4j-core', version: rootProject['verLog4j'] diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/MariaDBSourceConfig.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/MariaDBSourceConfig.java new file mode 100644 index 00000000..87736555 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/MariaDBSourceConfig.java @@ -0,0 +1,98 @@ +package pro.gravit.launchserver.auth; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.mariadb.jdbc.MariaDbDataSource; +import pro.gravit.utils.helper.VerifyHelper; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +import static java.util.concurrent.TimeUnit.MINUTES; + +public final class MariaDBSourceConfig implements AutoCloseable, SQLSourceConfig { + + public static final int TIMEOUT = VerifyHelper.verifyInt( + Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), + VerifyHelper.POSITIVE, "launcher.mysql.idleTimeout can't be <= 5000"); + private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt( + Integer.parseUnsignedInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))), + VerifyHelper.POSITIVE, "launcher.mysql.maxPoolSize can't be <= 0"); + + // Instance + private transient final String poolName; + private transient final Logger logger = LogManager.getLogger(); + + // Config + private String url; + private String username; + private String password; + private long hikariMaxLifetime = MINUTES.toMillis(30); + private boolean useHikari; + + // Cache + private transient DataSource source; + private transient boolean hikari; + + + public MariaDBSourceConfig(String poolName) { + this.poolName = poolName; + } + + public MariaDBSourceConfig(String poolName, String url, String username, String password) { + this.poolName = poolName; + this.url = url; + this.username = username; + this.password = password; + } + + public MariaDBSourceConfig(String poolName, DataSource source, boolean hikari) { + this.poolName = poolName; + this.source = source; + this.hikari = hikari; + } + + @Override + public synchronized void close() { + if (hikari) + ((HikariDataSource) source).close(); + } + + + public synchronized Connection getConnection() throws SQLException { + if (source == null) { // New data source + MariaDbDataSource mariaDbDataSource = new MariaDbDataSource(); + mariaDbDataSource.setUser(username); + mariaDbDataSource.setPassword(password); + mariaDbDataSource.setUrl(url); + hikari = false; + // Try using HikariCP + source = mariaDbDataSource; + if (useHikari) { + try { + Class.forName("com.zaxxer.hikari.HikariDataSource"); + hikari = true; // Used for shutdown. Not instanceof because of possible classpath error + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDataSource(mariaDbDataSource); + hikariConfig.setPoolName(poolName); + hikariConfig.setMinimumIdle(1); + hikariConfig.setMaximumPoolSize(MAX_POOL_SIZE); + hikariConfig.setConnectionTestQuery("SELECT 1"); + hikariConfig.setConnectionTimeout(1000); + hikariConfig.setLeakDetectionThreshold(2000); + hikariConfig.setMaxLifetime(hikariMaxLifetime); + // Set HikariCP pool + // Replace source with hds + source = new HikariDataSource(hikariConfig); + } catch (ClassNotFoundException ignored) { + logger.debug("HikariCP isn't in classpath for '{}'", poolName); + } + } + + } + return source.getConnection(); + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java index f1d8cf7d..a749a49b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/AuthCoreProvider.java @@ -42,6 +42,7 @@ public static void registerProviders() { if (!registredProviders) { providers.register("reject", RejectAuthCoreProvider.class); providers.register("mysql", MySQLCoreProvider.class); + providers.register("mariadb", MariaDBCoreProvider.class); providers.register("postgresql", PostgresSQLCoreProvider.class); providers.register("memory", MemoryAuthCoreProvider.class); providers.register("merge", MergeAuthCoreProvider.class); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MariaDBCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MariaDBCoreProvider.java new file mode 100644 index 00000000..19376fcd --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MariaDBCoreProvider.java @@ -0,0 +1,354 @@ +package pro.gravit.launchserver.auth.core; + +import pro.gravit.launcher.ClientPermissions; +import pro.gravit.launcher.request.secure.HardwareReportRequest; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.MariaDBSourceConfig; +import pro.gravit.launchserver.auth.MySQLSourceConfig; +import pro.gravit.launchserver.auth.SQLSourceConfig; +import pro.gravit.launchserver.auth.core.interfaces.UserHardware; +import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; +import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportHardware; +import pro.gravit.utils.helper.IOHelper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.sql.*; +import java.util.Base64; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +public class MariaDBCoreProvider extends AbstractSQLCoreProvider implements AuthSupportHardware { + public MariaDBSourceConfig mariaDBHolder; + + public String hardwareIdColumn; + public String tableHWID = "hwids"; + public String tableHWIDLog = "hwidLog"; + public double criticalCompareLevel = 1.0; + private transient String sqlFindHardwareByPublicKey; + private transient String sqlFindHardwareByData; + private transient String sqlFindHardwareById; + private transient String sqlCreateHardware; + private transient String sqlCreateHWIDLog; + private transient String sqlUpdateHardwarePublicKey; + private transient String sqlUpdateHardwareBanned; + private transient String sqlUpdateUsers; + private transient String sqlUsersByHwidId; + + @Override + public SQLSourceConfig getSQLConfig() { + return mariaDBHolder; + } + + @Override + public void init(LaunchServer server) { + super.init(server); + String userInfoCols = makeUserCols(); + String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey"; + if (sqlFindHardwareByPublicKey == null) + sqlFindHardwareByPublicKey = "SELECT %s FROM %s WHERE `publicKey` = ?".formatted(hardwareInfoCols, tableHWID); + if (sqlFindHardwareById == null) + sqlFindHardwareById = "SELECT %s FROM %s WHERE `id` = ?".formatted(hardwareInfoCols, tableHWID); + if (sqlUsersByHwidId == null) + sqlUsersByHwidId = "SELECT %s FROM %s WHERE `%s` = ?".formatted(userInfoCols, table, hardwareIdColumn); + if (sqlFindHardwareByData == null) + sqlFindHardwareByData = "SELECT %s FROM %s".formatted(hardwareInfoCols, tableHWID); + if (sqlCreateHardware == null) + sqlCreateHardware = "INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')".formatted(tableHWID); + if (sqlCreateHWIDLog == null) + sqlCreateHWIDLog = "INSERT INTO %s (`hwidId`, `newPublicKey`) VALUES (?, ?)".formatted(tableHWIDLog); + if (sqlUpdateHardwarePublicKey == null) + sqlUpdateHardwarePublicKey = "UPDATE %s SET `publicKey` = ? WHERE `id` = ?".formatted(tableHWID); + sqlUpdateHardwareBanned = "UPDATE %s SET `banned` = ? WHERE `id` = ?".formatted(tableHWID); + sqlUpdateUsers = "UPDATE %s SET `%s` = ? WHERE `%s` = ?".formatted(table, hardwareIdColumn, uuidColumn); + } + + @Override + protected String makeUserCols() { + return super.makeUserCols().concat(", ").concat(hardwareIdColumn); + } + + @Override + protected MariaDBUser constructUser(ResultSet set) throws SQLException { + return set.next() ? new MariaDBUser(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn), + set.getString(accessTokenColumn), set.getString(serverIDColumn), set.getString(passwordColumn), requestPermissions(set.getString(uuidColumn)), set.getLong(hardwareIdColumn)) : null; + } + + private MariaDBUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException { + HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo(); + hardwareInfo.hwDiskId = set.getString("hwDiskId"); + hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber"); + Blob displayId = set.getBlob("displayId"); + hardwareInfo.displayId = displayId == null ? null : IOHelper.read(displayId.getBinaryStream()); + hardwareInfo.bitness = set.getInt("bitness"); + hardwareInfo.totalMemory = set.getLong("totalMemory"); + hardwareInfo.logicalProcessors = set.getInt("logicalProcessors"); + hardwareInfo.physicalProcessors = set.getInt("physicalProcessors"); + hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq"); + hardwareInfo.battery = set.getBoolean("battery"); + hardwareInfo.graphicCard = set.getString("graphicCard"); + Blob publicKey = set.getBlob("publicKey"); + long id = set.getLong("id"); + boolean banned = set.getBoolean("banned"); + return new MariaDBUserHardware(hardwareInfo, publicKey == null ? null : IOHelper.read(publicKey.getBinaryStream()), id, banned); + } + + private void setUserHardwareId(Connection connection, UUID uuid, long hwidId) throws SQLException { + PreparedStatement s = connection.prepareStatement(sqlUpdateUsers); + s.setLong(1, hwidId); + s.setString(2, uuid.toString()); + s.executeUpdate(); + } + + @Override + public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) { + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + try (ResultSet set = s.executeQuery()) { + if (set.next()) { + return fetchHardwareInfo(set); + } else { + return null; + } + } + } catch (SQLException | IOException throwables) { + logger.error("SQL Error", throwables); + return null; + } + } + + @Override + public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) { + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData); + try (ResultSet set = s.executeQuery()) { + while (set.next()) { + MariaDBUserHardware hw = fetchHardwareInfo(set); + HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info); + if (result.compareLevel > criticalCompareLevel) { + return hw; + } + } + } + } catch (SQLException | IOException throwables) { + logger.error("SQL Error", throwables); + } + return null; + } + + @Override + public UserHardware getHardwareInfoById(String id) { + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareById); + s.setLong(1, Long.parseLong(id)); + try (ResultSet set = s.executeQuery()) { + if (set.next()) { + return fetchHardwareInfo(set); + } else { + return null; + } + } + } catch (SQLException | IOException throwables) { + logger.error("SQL Error", throwables); + return null; + } + } + + @Override + public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) { + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + s.setString(2, hardwareInfo.hwDiskId); + s.setString(3, hardwareInfo.baseboardSerialNumber); + s.setBlob(4, hardwareInfo.displayId == null ? null : new ByteArrayInputStream(hardwareInfo.displayId)); + s.setInt(5, hardwareInfo.bitness); + s.setLong(6, hardwareInfo.totalMemory); + s.setInt(7, hardwareInfo.logicalProcessors); + s.setInt(8, hardwareInfo.physicalProcessors); + s.setLong(9, hardwareInfo.processorMaxFreq); + s.setString(10, hardwareInfo.graphicCard); + s.setBoolean(11, hardwareInfo.battery); + s.executeUpdate(); + try (ResultSet generatedKeys = s.getGeneratedKeys()) { + if (generatedKeys.next()) { + //writeHwidLog(connection, generatedKeys.getLong(1), publicKey); + long id = generatedKeys.getLong(1); + return new MariaDBUserHardware(hardwareInfo, publicKey, id, false); + } + } + return null; + } catch (SQLException throwables) { + logger.error("SQL Error", throwables); + return null; + } + } + + @Override + public void connectUserAndHardware(UserSession userSession, UserHardware hardware) { + SQLUserSession mySQLUserSession = (SQLUserSession) userSession; + MariaDBUser mariaDBUser = (MariaDBUser) mySQLUserSession.getUser(); + MariaDBUserHardware mariaDBUserHardware = (MariaDBUserHardware) hardware; + if (mariaDBUser.hwidId == mariaDBUserHardware.id) return; + mariaDBUser.hwidId = mariaDBUserHardware.id; + try (Connection connection = mariaDBHolder.getConnection()) { + setUserHardwareId(connection, mariaDBUser.getUUID(), mariaDBUserHardware.id); + } catch (SQLException throwables) { + logger.error("SQL Error", throwables); + } + } + + @Override + public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) { + MariaDBUserHardware mariaDBUserHardware = (MariaDBUserHardware) hardware; + mariaDBUserHardware.publicKey = publicKey; + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + s.setLong(2, mariaDBUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL error", e); + } + } + + @Override + public Iterable getUsersByHardwareInfo(UserHardware hardware) { + List users = new LinkedList<>(); + try (Connection c = mariaDBHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(sqlUsersByHwidId); + s.setLong(1, Long.parseLong(hardware.getId())); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + try (ResultSet set = s.executeQuery()) { + while (!set.isLast()) { + users.add(constructUser(set)); + } + } + } catch (SQLException e) { + logger.error("SQL error", e); + return null; + } + return users; + } + + @Override + public void banHardware(UserHardware hardware) { + MariaDBUserHardware mariaDBUserHardware = (MariaDBUserHardware) hardware; + mariaDBUserHardware.banned = true; + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned); + s.setBoolean(1, true); + s.setLong(2, mariaDBUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL Error", e); + } + } + + @Override + public void unbanHardware(UserHardware hardware) { + MariaDBUserHardware mariaDBUserHardware = (MariaDBUserHardware) hardware; + mariaDBUserHardware.banned = false; + try (Connection connection = mariaDBHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned); + s.setBoolean(1, false); + s.setLong(2, mariaDBUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL error", e); + } + } + + @Override + protected SQLUserSession createSession(SQLUser user) { + return new MariaDBUserSession(user); + } + + public class MariaDBUserSession extends SQLUserSession implements UserSessionSupportHardware { + private transient MariaDBUser mariaDBUser; + protected transient MariaDBUserHardware hardware; + + public MariaDBUserSession(SQLUser user) { + super(user); + mariaDBUser = (MariaDBUser) user; + } + + @Override + public String getHardwareId() { + return mariaDBUser.hwidId == 0 ? null : String.valueOf(mariaDBUser.hwidId); + } + + @Override + public UserHardware getHardware() { + if(hardware == null) { + hardware = (MariaDBUserHardware) getHardwareInfoById(String.valueOf(mariaDBUser.hwidId)); + } + return hardware; + } + } + + public static class MariaDBUserHardware implements UserHardware { + private final HardwareReportRequest.HardwareInfo hardwareInfo; + private final long id; + private byte[] publicKey; + private boolean banned; + + public MariaDBUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) { + this.hardwareInfo = hardwareInfo; + this.publicKey = publicKey; + this.id = id; + this.banned = banned; + } + + @Override + public HardwareReportRequest.HardwareInfo getHardwareInfo() { + return hardwareInfo; + } + + @Override + public byte[] getPublicKey() { + return publicKey; + } + + @Override + public String getId() { + return String.valueOf(id); + } + + @Override + public boolean isBanned() { + return banned; + } + + @Override + public String toString() { + return "MySQLUserHardware{" + + "hardwareInfo=" + hardwareInfo + + ", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) + + ", id=" + id + + ", banned=" + banned + + '}'; + } + } + + public class MariaDBUser extends SQLUser { + protected long hwidId; + + public MariaDBUser(UUID uuid, String username, String accessToken, String serverId, String password, ClientPermissions permissions, long hwidId) { + super(uuid, username, accessToken, serverId, password, permissions); + this.hwidId = hwidId; + } + + @Override + public String toString() { + return "MySQLUser{" + + "uuid=" + uuid + + ", username='" + username + '\'' + + ", permissions=" + permissions + + ", hwidId=" + hwidId + + '}'; + } + } +} diff --git a/props.gradle b/props.gradle index 00e2987d..02843048 100644 --- a/props.gradle +++ b/props.gradle @@ -13,6 +13,7 @@ verSlf4j = '1.7.36' verLog4j = '2.24.3' verMySQLConn = '9.2.0' + verMariaDBConn = '3.5.1' verPostgreSQLConn = '42.7.5' verProguard = '7.6.1' verLaunch4j = '3.50'