620 lines
22 KiB
Java

package fr.xephi.authme.datasource;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.util.StringUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
*/
public class SQLite implements DataSource {
private final String database;
private final String tableName;
private final Columns col;
private Connection con;
/**
* Constructor for SQLite.
*
* @param settings The settings instance
*
* @throws ClassNotFoundException if no driver could be found for the datasource
* @throws SQLException when initialization of a SQL datasource failed
*/
public SQLite(NewSetting settings) throws ClassNotFoundException, SQLException {
this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
this.col = new Columns(settings);
try {
this.connect();
this.setup();
} catch (ClassNotFoundException | SQLException ex) {
ConsoleLogger.logException("Error during SQLite initialization:", ex);
throw ex;
}
}
@VisibleForTesting
SQLite(NewSetting settings, Connection connection) {
this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
this.col = new Columns(settings);
this.con = connection;
}
private static void logSqlException(SQLException e) {
ConsoleLogger.logException("Error while executing SQL statement:", e);
}
private synchronized void connect() throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
ConsoleLogger.info("SQLite driver loaded");
this.con = DriverManager.getConnection("jdbc:sqlite:plugins/AuthMe/" + database + ".db");
}
private synchronized void setup() throws SQLException {
Statement st = null;
ResultSet rs = null;
try {
st = con.createStatement();
st.executeUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " (" + col.ID + " INTEGER AUTO_INCREMENT," + col.NAME + " VARCHAR(255) NOT NULL UNIQUE," + col.PASSWORD + " VARCHAR(255) NOT NULL," + col.IP + " VARCHAR(40) NOT NULL," + col.LAST_LOGIN + " BIGINT," + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0'," + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0'," + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0'," + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT '" + Settings.defaultWorld + "'," + col.EMAIL + " VARCHAR(255) DEFAULT 'your@email.com'," + "CONSTRAINT table_const_prim PRIMARY KEY (" + col.ID + "));");
rs = con.getMetaData().getColumns(null, null, tableName, col.PASSWORD);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.PASSWORD + " VARCHAR(255) NOT NULL;");
}
rs.close();
if (!col.SALT.isEmpty()) {
rs = con.getMetaData().getColumns(null, null, tableName, col.SALT);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.SALT + " VARCHAR(255);");
}
rs.close();
}
rs = con.getMetaData().getColumns(null, null, tableName, col.IP);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.IP + " VARCHAR(40) NOT NULL;");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.LAST_LOGIN);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.LAST_LOGIN + " TIMESTAMP DEFAULT current_timestamp;");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.LASTLOC_X);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.LASTLOC_X + " DOUBLE NOT NULL DEFAULT '0.0';");
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.LASTLOC_Y + " DOUBLE NOT NULL DEFAULT '0.0';");
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.LASTLOC_Z + " DOUBLE NOT NULL DEFAULT '0.0';");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.LASTLOC_WORLD);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.LASTLOC_WORLD + " VARCHAR(255) NOT NULL DEFAULT 'world';");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.EMAIL);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.EMAIL + " VARCHAR(255) DEFAULT 'your@email.com';");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.IS_LOGGED);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.IS_LOGGED + " INT DEFAULT '0';");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, col.REAL_NAME);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + col.REAL_NAME + " VARCHAR(255) NOT NULL DEFAULT 'Player';");
}
} finally {
close(rs);
close(st);
}
ConsoleLogger.info("SQLite Setup finished");
}
@Override
public void reload() {
// TODO 20160309: Implement reloading
}
@Override
public synchronized boolean isAuthAvailable(String user) {
PreparedStatement pst = null;
ResultSet rs = null;
try {
pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=LOWER(?);");
pst.setString(1, user);
rs = pst.executeQuery();
return rs.next();
} catch (SQLException ex) {
ConsoleLogger.showError(ex.getMessage());
return false;
} finally {
close(rs);
close(pst);
}
}
@Override
public HashedPassword getPassword(String user) {
PreparedStatement pst = null;
ResultSet rs = null;
try {
pst = con.prepareStatement("SELECT " + col.PASSWORD + "," + col.SALT
+ " FROM " + tableName + " WHERE " + col.NAME + "=?");
pst.setString(1, user);
rs = pst.executeQuery();
if (rs.next()) {
return new HashedPassword(rs.getString(col.PASSWORD),
!col.SALT.isEmpty() ? rs.getString(col.SALT) : null);
}
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(rs);
close(pst);
}
return null;
}
@Override
public synchronized PlayerAuth getAuth(String user) {
PreparedStatement pst = null;
ResultSet rs = null;
try {
pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=LOWER(?);");
pst.setString(1, user);
rs = pst.executeQuery();
if (rs.next()) {
return buildAuthFromResultSet(rs);
}
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(rs);
close(pst);
}
return null;
}
@Override
public synchronized boolean saveAuth(PlayerAuth auth) {
PreparedStatement pst = null;
try {
HashedPassword password = auth.getPassword();
if (col.SALT.isEmpty()) {
if (!StringUtils.isEmpty(auth.getPassword().getSalt())) {
ConsoleLogger.showError("Warning! Detected hashed password with separate salt but the salt column "
+ "is not set in the config!");
}
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD +
"," + col.IP + "," + col.LAST_LOGIN + "," + col.REAL_NAME + "," + col.EMAIL +
") VALUES (?,?,?,?,?,?);");
pst.setString(1, auth.getNickname());
pst.setString(2, password.getHash());
pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin());
pst.setString(5, auth.getRealName());
pst.setString(6, auth.getEmail());
pst.executeUpdate();
} else {
pst = con.prepareStatement("INSERT INTO " + tableName + "(" + col.NAME + "," + col.PASSWORD + ","
+ col.IP + "," + col.LAST_LOGIN + "," + col.REAL_NAME + "," + col.EMAIL + "," + col.SALT
+ ") VALUES (?,?,?,?,?,?,?);");
pst.setString(1, auth.getNickname());
pst.setString(2, password.getHash());
pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin());
pst.setString(5, auth.getRealName());
pst.setString(6, auth.getEmail());
pst.setString(7, password.getSalt());
pst.executeUpdate();
}
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
return true;
}
@Override
public synchronized boolean updatePassword(PlayerAuth auth) {
return updatePassword(auth.getNickname(), auth.getPassword());
}
@Override
public boolean updatePassword(String user, HashedPassword password) {
user = user.toLowerCase();
PreparedStatement pst = null;
try {
boolean useSalt = !col.SALT.isEmpty();
String sql = "UPDATE " + tableName + " SET " + col.PASSWORD + " = ?"
+ (useSalt ? ", " + col.SALT + " = ?" : "")
+ " WHERE " + col.NAME + " = ?";
pst = con.prepareStatement(sql);
pst.setString(1, password.getHash());
if (useSalt) {
pst.setString(2, password.getSalt());
pst.setString(3, user);
} else {
pst.setString(2, user);
}
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
return false;
}
@Override
public boolean updateSession(PlayerAuth auth) {
PreparedStatement pst = null;
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + col.IP + "=?, " + col.LAST_LOGIN + "=?, " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;");
pst.setString(1, auth.getIp());
pst.setLong(2, auth.getLastLogin());
pst.setString(3, auth.getRealName());
pst.setString(4, auth.getNickname());
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
return false;
}
@Override
public List<String> autoPurgeDatabase(long until) {
List<String> list = new ArrayList<>();
String select = "SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.LAST_LOGIN + "<?;";
String delete = "DELETE FROM " + tableName + " WHERE " + col.LAST_LOGIN + "<?;";
try (PreparedStatement selectPst = con.prepareStatement(select);
PreparedStatement deletePst = con.prepareStatement(delete)) {
selectPst.setLong(1, until);
try (ResultSet rs = selectPst.executeQuery()) {
while (rs.next()) {
list.add(rs.getString(col.NAME));
}
}
deletePst.setLong(1, until);
deletePst.executeUpdate();
} catch (SQLException ex) {
logSqlException(ex);
}
return list;
}
@Override
public synchronized boolean removeAuth(String user) {
PreparedStatement pst = null;
try {
pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;");
pst.setString(1, user);
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
return false;
}
@Override
public boolean updateQuitLoc(PlayerAuth auth) {
PreparedStatement pst = null;
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + col.LASTLOC_X + "=?, " + col.LASTLOC_Y + "=?, " + col.LASTLOC_Z + "=?, " + col.LASTLOC_WORLD + "=? WHERE " + col.NAME + "=?;");
pst.setDouble(1, auth.getQuitLocX());
pst.setDouble(2, auth.getQuitLocY());
pst.setDouble(3, auth.getQuitLocZ());
pst.setString(4, auth.getWorld());
pst.setString(5, auth.getNickname());
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
return false;
}
@Override
public boolean updateEmail(PlayerAuth auth) {
String sql = "UPDATE " + tableName + " SET " + col.EMAIL + "=? WHERE " + col.NAME + "=?;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, auth.getEmail());
pst.setString(2, auth.getNickname());
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
}
return false;
}
@Override
public synchronized void close() {
try {
if (con != null && !con.isClosed()) {
con.close();
}
} catch (SQLException ex) {
logSqlException(ex);
}
}
private void close(Statement st) {
if (st != null) {
try {
st.close();
} catch (SQLException ex) {
logSqlException(ex);
}
}
}
private void close(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException ex) {
logSqlException(ex);
}
}
}
@Override
public List<String> getAllAuthsByIp(String ip) {
PreparedStatement pst = null;
ResultSet rs = null;
List<String> countIp = new ArrayList<>();
try {
pst = con.prepareStatement("SELECT " + col.NAME + " FROM " + tableName + " WHERE " + col.IP + "=?;");
pst.setString(1, ip);
rs = pst.executeQuery();
while (rs.next()) {
countIp.add(rs.getString(col.NAME));
}
return countIp;
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(rs);
close(pst);
}
return new ArrayList<>();
}
@Override
public int countAuthsByEmail(String email) {
String sql = "SELECT COUNT(1) FROM " + tableName + " WHERE " + col.EMAIL + " = ? COLLATE NOCASE;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, email);
try (ResultSet rs = pst.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
}
} catch (SQLException ex) {
logSqlException(ex);
}
return 0;
}
@Override
public void purgeBanned(List<String> banned) {
String sql = "DELETE FROM " + tableName + " WHERE " + col.NAME + "=?;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
for (String name : banned) {
pst.setString(1, name);
pst.executeUpdate();
}
} catch (SQLException ex) {
logSqlException(ex);
}
}
@Override
public DataSourceType getType() {
return DataSourceType.SQLITE;
}
@Override
public boolean isLogged(String user) {
PreparedStatement pst = null;
ResultSet rs = null;
try {
pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE LOWER(" + col.NAME + ")=?;");
pst.setString(1, user);
rs = pst.executeQuery();
if (rs.next())
return (rs.getInt(col.IS_LOGGED) == 1);
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(rs);
close(pst);
}
return false;
}
@Override
public void setLogged(String user) {
PreparedStatement pst = null;
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE LOWER(" + col.NAME + ")=?;");
pst.setInt(1, 1);
pst.setString(2, user);
pst.executeUpdate();
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
}
@Override
public void setUnlogged(String user) {
PreparedStatement pst = null;
if (user != null)
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE LOWER(" + col.NAME + ")=?;");
pst.setInt(1, 0);
pst.setString(2, user);
pst.executeUpdate();
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
}
@Override
public void purgeLogged() {
PreparedStatement pst = null;
try {
pst = con.prepareStatement("UPDATE " + tableName + " SET " + col.IS_LOGGED + "=? WHERE " + col.IS_LOGGED + "=?;");
pst.setInt(1, 0);
pst.setInt(2, 1);
pst.executeUpdate();
} catch (SQLException ex) {
logSqlException(ex);
} finally {
close(pst);
}
}
@Override
public int getAccountsRegistered() {
String sql = "SELECT COUNT(*) FROM " + tableName + ";";
try (PreparedStatement pst = con.prepareStatement(sql); ResultSet rs = pst.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
} catch (SQLException ex) {
logSqlException(ex);
}
return 0;
}
@Override
public boolean updateRealName(String user, String realName) {
String sql = "UPDATE " + tableName + " SET " + col.REAL_NAME + "=? WHERE " + col.NAME + "=?;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, realName);
pst.setString(2, user);
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
}
return false;
}
@Override
public boolean updateIp(String user, String ip) {
String sql = "UPDATE " + tableName + " SET " + col.IP + "=? WHERE " + col.NAME + "=?;";
try (PreparedStatement pst = con.prepareStatement(sql)) {
pst.setString(1, ip);
pst.setString(2, user);
pst.executeUpdate();
return true;
} catch (SQLException ex) {
logSqlException(ex);
}
return false;
}
@Override
public List<PlayerAuth> getAllAuths() {
List<PlayerAuth> auths = new ArrayList<>();
String sql = "SELECT * FROM " + tableName + ";";
try (PreparedStatement pst = con.prepareStatement(sql); ResultSet rs = pst.executeQuery()) {
while (rs.next()) {
PlayerAuth auth = buildAuthFromResultSet(rs);
auths.add(auth);
}
} catch (SQLException ex) {
logSqlException(ex);
}
return auths;
}
@Override
public List<PlayerAuth> getLoggedPlayers() {
List<PlayerAuth> auths = new ArrayList<>();
String sql = "SELECT * FROM " + tableName + " WHERE " + col.IS_LOGGED + "=1;";
try (PreparedStatement pst = con.prepareStatement(sql); ResultSet rs = pst.executeQuery()) {
while (rs.next()) {
PlayerAuth auth = buildAuthFromResultSet(rs);
auths.add(auth);
}
} catch (SQLException ex) {
logSqlException(ex);
}
return auths;
}
@Override
public synchronized boolean isEmailStored(String email) {
String sql = "SELECT 1 FROM " + tableName + " WHERE " + col.EMAIL + " = ? COLLATE NOCASE;";
ResultSet rs = null;
try (PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, email);
rs = ps.executeQuery();
return rs.next();
} catch (SQLException e) {
logSqlException(e);
} finally {
close(rs);
}
return false;
}
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
PlayerAuth.Builder authBuilder = PlayerAuth.builder()
.name(row.getString(col.NAME))
.email(row.getString(col.EMAIL))
.realName(row.getString(col.REAL_NAME))
.password(row.getString(col.PASSWORD), salt)
.lastLogin(row.getLong(col.LAST_LOGIN))
.locX(row.getDouble(col.LASTLOC_X))
.locY(row.getDouble(col.LASTLOC_Y))
.locZ(row.getDouble(col.LASTLOC_Z))
.locWorld(row.getString(col.LASTLOC_WORLD));
String ip = row.getString(col.IP);
if (!ip.isEmpty()) {
authBuilder.ip(ip);
}
return authBuilder.build();
}
}