diff options
Diffstat (limited to 'src/database')
-rw-r--r-- | src/database/database-files.cpp | 103 | ||||
-rw-r--r-- | src/database/database-files.h | 21 | ||||
-rw-r--r-- | src/database/database-sqlite3.cpp | 167 | ||||
-rw-r--r-- | src/database/database-sqlite3.h | 41 | ||||
-rw-r--r-- | src/database/database.h | 23 |
5 files changed, 355 insertions, 0 deletions
diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index 70de8c8d2..64eca394e 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "porting.h" #include "filesys.h" +#include "util/string.h" // !!! WARNING !!! // This backend is intended to be used on Minetest 0.4.16 only for the transition backend @@ -177,3 +178,105 @@ void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res) res.emplace_back(player.getName()); } } + +AuthDatabaseFiles::AuthDatabaseFiles(const std::string &savedir) : m_savedir(savedir) +{ + readAuthFile(); +} + +bool AuthDatabaseFiles::getAuth(const std::string &name, AuthEntry &res) +{ + const auto res_i = m_auth_list.find(name); + if (res_i == m_auth_list.end()) { + return false; + } + res = res_i->second; + return true; +} + +bool AuthDatabaseFiles::saveAuth(const AuthEntry &authEntry) +{ + m_auth_list[authEntry.name] = authEntry; + + // save entire file + return writeAuthFile(); +} + +bool AuthDatabaseFiles::createAuth(AuthEntry &authEntry) +{ + m_auth_list[authEntry.name] = authEntry; + + // save entire file + return writeAuthFile(); +} + +bool AuthDatabaseFiles::deleteAuth(const std::string &name) +{ + if (!m_auth_list.erase(name)) { + // did not delete anything -> hadn't existed + return false; + } + return writeAuthFile(); +} + +void AuthDatabaseFiles::listNames(std::vector<std::string> &res) +{ + res.clear(); + res.reserve(m_auth_list.size()); + for (const auto &res_pair : m_auth_list) { + res.push_back(res_pair.first); + } +} + +void AuthDatabaseFiles::reload() +{ + readAuthFile(); +} + +bool AuthDatabaseFiles::readAuthFile() +{ + std::string path = m_savedir + DIR_DELIM + "auth.txt"; + std::ifstream file(path, std::ios::binary); + if (!file.good()) { + return false; + } + m_auth_list.clear(); + while (file.good()) { + std::string line; + std::getline(file, line); + std::vector<std::string> parts = str_split(line, ':'); + if (parts.size() < 3) // also: empty line at end + continue; + const std::string &name = parts[0]; + const std::string &password = parts[1]; + std::vector<std::string> privileges = str_split(parts[2], ','); + s64 last_login = parts.size() > 3 ? atol(parts[3].c_str()) : 0; + + m_auth_list[name] = { + 1, + name, + password, + privileges, + last_login, + }; + } + return true; +} + +bool AuthDatabaseFiles::writeAuthFile() +{ + std::string path = m_savedir + DIR_DELIM + "auth.txt"; + std::ostringstream output(std::ios_base::binary); + for (const auto &auth_i : m_auth_list) { + const AuthEntry &authEntry = auth_i.second; + output << authEntry.name << ":" << authEntry.password << ":"; + output << str_join(authEntry.privileges, ","); + output << ":" << authEntry.last_login; + output << std::endl; + } + if (!fs::safeWriteToFile(path, output.str())) { + infostream << "Failed to write " << path << std::endl; + return false; + } + return true; +} diff --git a/src/database/database-files.h b/src/database/database-files.h index f0824a304..218815cf7 100644 --- a/src/database/database-files.h +++ b/src/database/database-files.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., // for player files #include "database.h" +#include <unordered_map> class PlayerDatabaseFiles : public PlayerDatabase { @@ -41,3 +42,23 @@ private: std::string m_savedir; }; + +class AuthDatabaseFiles : public AuthDatabase +{ +public: + AuthDatabaseFiles(const std::string &savedir); + virtual ~AuthDatabaseFiles() = default; + + virtual bool getAuth(const std::string &name, AuthEntry &res); + virtual bool saveAuth(const AuthEntry &authEntry); + virtual bool createAuth(AuthEntry &authEntry); + virtual bool deleteAuth(const std::string &name); + virtual void listNames(std::vector<std::string> &res); + virtual void reload(); + +private: + std::unordered_map<std::string, AuthEntry> m_auth_list; + std::string m_savedir; + bool readAuthFile(); + bool writeAuthFile(); +}; diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 76935ada4..97b0fd36a 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -606,3 +606,170 @@ void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res) sqlite3_reset(m_stmt_player_list); } + +/* + * Auth database + */ + +AuthDatabaseSQLite3::AuthDatabaseSQLite3(const std::string &savedir) : + Database_SQLite3(savedir, "auth"), AuthDatabase() +{ +} + +AuthDatabaseSQLite3::~AuthDatabaseSQLite3() +{ + FINALIZE_STATEMENT(m_stmt_read) + FINALIZE_STATEMENT(m_stmt_write) + FINALIZE_STATEMENT(m_stmt_create) + FINALIZE_STATEMENT(m_stmt_delete) + FINALIZE_STATEMENT(m_stmt_list_names) + FINALIZE_STATEMENT(m_stmt_read_privs) + FINALIZE_STATEMENT(m_stmt_write_privs) + FINALIZE_STATEMENT(m_stmt_delete_privs) + FINALIZE_STATEMENT(m_stmt_last_insert_rowid) +} + +void AuthDatabaseSQLite3::createDatabase() +{ + assert(m_database); // Pre-condition + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `auth` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT," + "`name` VARCHAR(32) UNIQUE," + "`password` VARCHAR(512)," + "`last_login` INTEGER" + ");", + NULL, NULL, NULL), + "Failed to create auth table"); + + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `user_privileges` (" + "`id` INTEGER," + "`privilege` VARCHAR(32)," + "PRIMARY KEY (id, privilege)" + "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE" + ");", + NULL, NULL, NULL), + "Failed to create auth privileges table"); +} + +void AuthDatabaseSQLite3::initStatements() +{ + PREPARE_STATEMENT(read, "SELECT id, name, password, last_login FROM auth WHERE name = ?"); + PREPARE_STATEMENT(write, "UPDATE auth set name = ?, password = ?, last_login = ? WHERE id = ?"); + PREPARE_STATEMENT(create, "INSERT INTO auth (name, password, last_login) VALUES (?, ?, ?)"); + PREPARE_STATEMENT(delete, "DELETE FROM auth WHERE name = ?"); + + PREPARE_STATEMENT(list_names, "SELECT name FROM auth ORDER BY name DESC"); + + PREPARE_STATEMENT(read_privs, "SELECT privilege FROM user_privileges WHERE id = ?"); + PREPARE_STATEMENT(write_privs, "INSERT OR IGNORE INTO user_privileges (id, privilege) VALUES (?, ?)"); + PREPARE_STATEMENT(delete_privs, "DELETE FROM user_privileges WHERE id = ?"); + + PREPARE_STATEMENT(last_insert_rowid, "SELECT last_insert_rowid()"); +} + +bool AuthDatabaseSQLite3::getAuth(const std::string &name, AuthEntry &res) +{ + verifyDatabase(); + str_to_sqlite(m_stmt_read, 1, name); + if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { + sqlite3_reset(m_stmt_read); + return false; + } + res.id = sqlite_to_uint(m_stmt_read, 0); + res.name = sqlite_to_string(m_stmt_read, 1); + res.password = sqlite_to_string(m_stmt_read, 2); + res.last_login = sqlite_to_int64(m_stmt_read, 3); + sqlite3_reset(m_stmt_read); + + int64_to_sqlite(m_stmt_read_privs, 1, res.id); + while (sqlite3_step(m_stmt_read_privs) == SQLITE_ROW) { + res.privileges.emplace_back(sqlite_to_string(m_stmt_read_privs, 0)); + } + sqlite3_reset(m_stmt_read_privs); + + return true; +} + +bool AuthDatabaseSQLite3::saveAuth(const AuthEntry &authEntry) +{ + beginSave(); + + str_to_sqlite(m_stmt_write, 1, authEntry.name); + str_to_sqlite(m_stmt_write, 2, authEntry.password); + int64_to_sqlite(m_stmt_write, 3, authEntry.last_login); + int64_to_sqlite(m_stmt_write, 4, authEntry.id); + sqlite3_vrfy(sqlite3_step(m_stmt_write), SQLITE_DONE); + sqlite3_reset(m_stmt_write); + + writePrivileges(authEntry); + + endSave(); + return true; +} + +bool AuthDatabaseSQLite3::createAuth(AuthEntry &authEntry) +{ + beginSave(); + + // id autoincrements + str_to_sqlite(m_stmt_create, 1, authEntry.name); + str_to_sqlite(m_stmt_create, 2, authEntry.password); + int64_to_sqlite(m_stmt_create, 3, authEntry.last_login); + sqlite3_vrfy(sqlite3_step(m_stmt_create), SQLITE_DONE); + sqlite3_reset(m_stmt_create); + + // obtain id and write back to original authEntry + sqlite3_step(m_stmt_last_insert_rowid); + authEntry.id = sqlite_to_uint(m_stmt_last_insert_rowid, 0); + sqlite3_reset(m_stmt_last_insert_rowid); + + writePrivileges(authEntry); + + endSave(); + return true; +} + +bool AuthDatabaseSQLite3::deleteAuth(const std::string &name) +{ + verifyDatabase(); + + str_to_sqlite(m_stmt_delete, 1, name); + sqlite3_vrfy(sqlite3_step(m_stmt_delete), SQLITE_DONE); + int changes = sqlite3_changes(m_database); + sqlite3_reset(m_stmt_delete); + + // privileges deleted by foreign key on delete cascade + + return changes > 0; +} + +void AuthDatabaseSQLite3::listNames(std::vector<std::string> &res) +{ + verifyDatabase(); + + while (sqlite3_step(m_stmt_list_names) == SQLITE_ROW) { + res.push_back(sqlite_to_string(m_stmt_list_names, 0)); + } + sqlite3_reset(m_stmt_list_names); +} + +void AuthDatabaseSQLite3::reload() +{ + // noop for SQLite +} + +void AuthDatabaseSQLite3::writePrivileges(const AuthEntry &authEntry) +{ + int64_to_sqlite(m_stmt_delete_privs, 1, authEntry.id); + sqlite3_vrfy(sqlite3_step(m_stmt_delete_privs), SQLITE_DONE); + sqlite3_reset(m_stmt_delete_privs); + for (const std::string &privilege : authEntry.privileges) { + int64_to_sqlite(m_stmt_write_privs, 1, authEntry.id); + str_to_sqlite(m_stmt_write_privs, 2, privilege); + sqlite3_vrfy(sqlite3_step(m_stmt_write_privs), SQLITE_DONE); + sqlite3_reset(m_stmt_write_privs); + } +} diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index 8d9f91f21..d7202a918 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -85,6 +85,16 @@ protected: return (u32) sqlite3_column_int(s, iCol); } + inline s64 sqlite_to_int64(sqlite3_stmt *s, int iCol) + { + return (s64) sqlite3_column_int64(s, iCol); + } + + inline u64 sqlite_to_uint64(sqlite3_stmt *s, int iCol) + { + return (u64) sqlite3_column_int64(s, iCol); + } + inline float sqlite_to_float(sqlite3_stmt *s, int iCol) { return (float) sqlite3_column_double(s, iCol); @@ -191,3 +201,34 @@ private: sqlite3_stmt *m_stmt_player_metadata_remove = nullptr; sqlite3_stmt *m_stmt_player_metadata_add = nullptr; }; + +class AuthDatabaseSQLite3 : private Database_SQLite3, public AuthDatabase +{ +public: + AuthDatabaseSQLite3(const std::string &savedir); + virtual ~AuthDatabaseSQLite3(); + + virtual bool getAuth(const std::string &name, AuthEntry &res); + virtual bool saveAuth(const AuthEntry &authEntry); + virtual bool createAuth(AuthEntry &authEntry); + virtual bool deleteAuth(const std::string &name); + virtual void listNames(std::vector<std::string> &res); + virtual void reload(); + +protected: + virtual void createDatabase(); + virtual void initStatements(); + +private: + virtual void writePrivileges(const AuthEntry &authEntry); + + sqlite3_stmt *m_stmt_read = nullptr; + sqlite3_stmt *m_stmt_write = nullptr; + sqlite3_stmt *m_stmt_create = nullptr; + sqlite3_stmt *m_stmt_delete = nullptr; + sqlite3_stmt *m_stmt_list_names = nullptr; + sqlite3_stmt *m_stmt_read_privs = nullptr; + sqlite3_stmt *m_stmt_write_privs = nullptr; + sqlite3_stmt *m_stmt_delete_privs = nullptr; + sqlite3_stmt *m_stmt_last_insert_rowid = nullptr; +}; diff --git a/src/database/database.h b/src/database/database.h index 9926c7b93..b7d551935 100644 --- a/src/database/database.h +++ b/src/database/database.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <set> #include <string> #include <vector> #include "irr_v3d.h" @@ -61,3 +62,25 @@ public: virtual bool removePlayer(const std::string &name) = 0; virtual void listPlayers(std::vector<std::string> &res) = 0; }; + +struct AuthEntry +{ + u64 id; + std::string name; + std::string password; + std::vector<std::string> privileges; + s64 last_login; +}; + +class AuthDatabase +{ +public: + virtual ~AuthDatabase() = default; + + virtual bool getAuth(const std::string &name, AuthEntry &res) = 0; + virtual bool saveAuth(const AuthEntry &authEntry) = 0; + virtual bool createAuth(AuthEntry &authEntry) = 0; + virtual bool deleteAuth(const std::string &name) = 0; + virtual void listNames(std::vector<std::string> &res) = 0; + virtual void reload() = 0; +}; |