[FEATURE] PasswordVerifier and MySQLCoreProvider

This commit is contained in:
Gravita 2021-05-20 02:18:01 +07:00
parent 9da0ca8604
commit d9f8b20a71
9 changed files with 271 additions and 2 deletions

View file

@ -11,6 +11,7 @@
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.handler.AuthHandler;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider;
import pro.gravit.launchserver.auth.provider.AuthProvider;
@ -203,6 +204,7 @@ public static void initGson(LaunchServerModulesManager modulesManager) {
@SuppressWarnings("deprecation")
public static void registerAll() {
AuthCoreProvider.registerProviders();
PasswordVerifier.registerProviders();
AuthHandler.registerHandlers();
AuthProvider.registerProviders();
TextureProvider.registerProviders();

View file

@ -24,6 +24,7 @@ public abstract class AuthCoreProvider implements AutoCloseable {
public static void registerProviders() {
if (!registredProviders) {
providers.register("reject", RejectAuthCoreProvider.class);
providers.register("mysql", MySQLCoreProvider.class);
registredProviders = true;
}
}
@ -33,7 +34,7 @@ public static void registerProviders() {
public abstract PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password);
public abstract void init(LaunchServer server);
// Auth Handler methods
protected abstract boolean updateAuth(User user) throws IOException;
protected abstract boolean updateAuth(User user, String accessToken) throws IOException;
protected abstract boolean updateServerID(User user, String serverID) throws IOException;
public List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> getDetails(Client client) {

View file

@ -0,0 +1,185 @@
package pro.gravit.launchserver.auth.core;
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.handler.CachedAuthHandler;
import pro.gravit.launchserver.auth.password.DigestPasswordVerifier;
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public class MySQLCoreProvider extends AuthCoreProvider {
private transient final Logger logger = LogManager.getLogger();
public MySQLSourceConfig mySQLHolder;
public String uuidColumn;
public String usernameColumn;
public String accessTokenColumn;
public String passwordColumn;
public String serverIDColumn;
public String table;
public DigestPasswordVerifier passwordVerifier;
// Prepared SQL queries
private transient String queryByUUIDSQL;
private transient String queryByUsernameSQL;
private transient String updateAuthSQL;
private transient String updateServerIDSQL;
@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 void verifyAuth(AuthResponse.AuthContext context) throws AuthException {
}
@Override
public PasswordVerifyReport verifyPassword(User user, AuthRequest.AuthPasswordInterface password) {
if(passwordVerifier.check(((MySQLUser)user).password, ((AuthPlainPassword)password).password)) {
return new PasswordVerifyReport(true);
} else {
return PasswordVerifyReport.FAILED;
}
}
@Override
public void init(LaunchServer 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 (table == null) logger.error("table cannot be null");
// Prepare SQL queries
queryByUUIDSQL = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, table, uuidColumn);
queryByUsernameSQL = String.format("SELECT %s, %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1",
uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, passwordColumn, table, usernameColumn);
updateAuthSQL = String.format("UPDATE %s SET %s=?, %s=NULL WHERE %s=? LIMIT 1",
table, accessTokenColumn, serverIDColumn, uuidColumn);
updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=? LIMIT 1",
table, serverIDColumn, uuidColumn);
}
@Override
protected boolean updateAuth(User user, String accessToken) throws IOException {
try (Connection c = mySQLHolder.getConnection()) {
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()) {
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()) : null;
}
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);
}
}
public static class MySQLUser implements User {
private final UUID uuid;
private final String username;
private final String accessToken;
private final String serverId;
private final String password;
private final ClientPermissions permissions;
public MySQLUser(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;
}
}
}

View file

@ -35,7 +35,7 @@ public void init(LaunchServer server) {
}
@Override
protected boolean updateAuth(User user) throws IOException {
protected boolean updateAuth(User user, String accessToken) throws IOException {
return false;
}

View file

@ -0,0 +1,26 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.SecurityHelper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class DigestPasswordVerifier extends PasswordVerifier {
private transient final Logger logger = LogManager.getLogger();
public String algo;
@Override
public boolean check(String encryptedPassword, String password) {
try {
MessageDigest digest = MessageDigest.getInstance(algo);
byte[] bytes = SecurityHelper.fromHex(encryptedPassword);
return Arrays.equals(password.getBytes(StandardCharsets.UTF_8), digest.digest(bytes));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return false;
}
}
}

View file

@ -0,0 +1,28 @@
package pro.gravit.launchserver.auth.password;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.utils.helper.SecurityHelper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class DoubleDigestPasswordVerifier extends PasswordVerifier {
private transient final Logger logger = LogManager.getLogger();
public String algo;
public boolean toHexMode;
@Override
public boolean check(String encryptedPassword, String password) {
try {
MessageDigest digest = MessageDigest.getInstance(algo);
byte[] bytes = SecurityHelper.fromHex(encryptedPassword);
byte[] firstDigest = digest.digest(bytes);
return Arrays.equals(password.getBytes(StandardCharsets.UTF_8), toHexMode ? digest.digest(SecurityHelper.toHex(firstDigest).getBytes(StandardCharsets.UTF_8)) : digest.digest(firstDigest));
} catch (NoSuchAlgorithmException e) {
logger.error("Digest algorithm {} not supported", algo);
return false;
}
}
}

View file

@ -0,0 +1,17 @@
package pro.gravit.launchserver.auth.password;
import pro.gravit.utils.ProviderMap;
public abstract class PasswordVerifier {
public static final ProviderMap<PasswordVerifier> providers = new ProviderMap<>("PasswordVerifier");
private static boolean registeredProviders = false;
public static void registerProviders() {
if(!registeredProviders) {
providers.register("plain", PlainPasswordVerifier.class);
providers.register("digest", DigestPasswordVerifier.class);
providers.register("doubleDigest", DoubleDigestPasswordVerifier.class);
registeredProviders = true;
}
}
public abstract boolean check(String encryptedPassword, String password);
}

View file

@ -0,0 +1,8 @@
package pro.gravit.launchserver.auth.password;
public class PlainPasswordVerifier extends PasswordVerifier {
@Override
public boolean check(String encryptedPassword, String password) {
return encryptedPassword.equals(password);
}
}

View file

@ -11,6 +11,7 @@
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.handler.AuthHandler;
import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider;
import pro.gravit.launchserver.auth.provider.AuthProvider;
@ -39,6 +40,7 @@ public void registerAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers));
builder.registerTypeAdapter(AuthHandler.class, new UniversalJsonAdapter<>(AuthHandler.providers));
builder.registerTypeAdapter(AuthCoreProvider.class, new UniversalJsonAdapter<>(AuthCoreProvider.providers));
builder.registerTypeAdapter(PasswordVerifier.class, new UniversalJsonAdapter<>(PasswordVerifier.providers));
builder.registerTypeAdapter(Component.class, new UniversalJsonAdapter<>(Component.providers));
builder.registerTypeAdapter(ProtectHandler.class, new UniversalJsonAdapter<>(ProtectHandler.providers));
builder.registerTypeAdapter(DaoProvider.class, new UniversalJsonAdapter<>(DaoProvider.providers));