forked from Mirrorlandia_minetest/minetest
Replace auth.txt with SQLite auth database (#7279)
* Replace auth.txt with SQLite auth database
This commit is contained in:
parent
1836882495
commit
153fb211ac
@ -251,6 +251,7 @@ LOCAL_SRC_FILES := \
|
|||||||
jni/src/util/srp.cpp \
|
jni/src/util/srp.cpp \
|
||||||
jni/src/util/timetaker.cpp \
|
jni/src/util/timetaker.cpp \
|
||||||
jni/src/unittest/test.cpp \
|
jni/src/unittest/test.cpp \
|
||||||
|
jni/src/unittest/test_authdatabase.cpp \
|
||||||
jni/src/unittest/test_collision.cpp \
|
jni/src/unittest/test_collision.cpp \
|
||||||
jni/src/unittest/test_compression.cpp \
|
jni/src/unittest/test_compression.cpp \
|
||||||
jni/src/unittest/test_connection.cpp \
|
jni/src/unittest/test_connection.cpp \
|
||||||
@ -331,6 +332,7 @@ LOCAL_SRC_FILES += \
|
|||||||
jni/src/script/cpp_api/s_security.cpp \
|
jni/src/script/cpp_api/s_security.cpp \
|
||||||
jni/src/script/cpp_api/s_server.cpp \
|
jni/src/script/cpp_api/s_server.cpp \
|
||||||
jni/src/script/lua_api/l_areastore.cpp \
|
jni/src/script/lua_api/l_areastore.cpp \
|
||||||
|
jni/src/script/lua_api/l_auth.cpp \
|
||||||
jni/src/script/lua_api/l_base.cpp \
|
jni/src/script/lua_api/l_base.cpp \
|
||||||
jni/src/script/lua_api/l_camera.cpp \
|
jni/src/script/lua_api/l_camera.cpp \
|
||||||
jni/src/script/lua_api/l_client.cpp \
|
jni/src/script/lua_api/l_client.cpp \
|
||||||
|
@ -4,72 +4,22 @@
|
|||||||
-- Builtin authentication handler
|
-- Builtin authentication handler
|
||||||
--
|
--
|
||||||
|
|
||||||
local auth_file_path = core.get_worldpath().."/auth.txt"
|
-- Make the auth object private, deny access to mods
|
||||||
local auth_table = {}
|
local core_auth = core.auth
|
||||||
|
core.auth = nil
|
||||||
local function read_auth_file()
|
|
||||||
local newtable = {}
|
|
||||||
local file, errmsg = io.open(auth_file_path, 'rb')
|
|
||||||
if not file then
|
|
||||||
core.log("info", auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for line in file:lines() do
|
|
||||||
if line ~= "" then
|
|
||||||
local fields = line:split(":", true)
|
|
||||||
local name, password, privilege_string, last_login = unpack(fields)
|
|
||||||
last_login = tonumber(last_login)
|
|
||||||
if not (name and password and privilege_string) then
|
|
||||||
error("Invalid line in auth.txt: "..dump(line))
|
|
||||||
end
|
|
||||||
local privileges = core.string_to_privs(privilege_string)
|
|
||||||
newtable[name] = {password=password, privileges=privileges, last_login=last_login}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
io.close(file)
|
|
||||||
auth_table = newtable
|
|
||||||
core.notify_authentication_modified()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function save_auth_file()
|
|
||||||
local newtable = {}
|
|
||||||
-- Check table for validness before attempting to save
|
|
||||||
for name, stuff in pairs(auth_table) do
|
|
||||||
assert(type(name) == "string")
|
|
||||||
assert(name ~= "")
|
|
||||||
assert(type(stuff) == "table")
|
|
||||||
assert(type(stuff.password) == "string")
|
|
||||||
assert(type(stuff.privileges) == "table")
|
|
||||||
assert(stuff.last_login == nil or type(stuff.last_login) == "number")
|
|
||||||
end
|
|
||||||
local content = {}
|
|
||||||
for name, stuff in pairs(auth_table) do
|
|
||||||
local priv_string = core.privs_to_string(stuff.privileges)
|
|
||||||
local parts = {name, stuff.password, priv_string, stuff.last_login or ""}
|
|
||||||
content[#content + 1] = table.concat(parts, ":")
|
|
||||||
end
|
|
||||||
if not core.safe_file_write(auth_file_path, table.concat(content, "\n")) then
|
|
||||||
error(auth_file_path.." could not be written to")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
read_auth_file()
|
|
||||||
|
|
||||||
core.builtin_auth_handler = {
|
core.builtin_auth_handler = {
|
||||||
get_auth = function(name)
|
get_auth = function(name)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
-- Figure out what password to use for a new player (singleplayer
|
local auth_entry = core_auth.read(name)
|
||||||
-- always has an empty password, otherwise use default, which is
|
-- If no such auth found, return nil
|
||||||
-- usually empty too)
|
if not auth_entry then
|
||||||
local new_password_hash = ""
|
|
||||||
-- If not in authentication table, return nil
|
|
||||||
if not auth_table[name] then
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
-- Figure out what privileges the player should have.
|
-- Figure out what privileges the player should have.
|
||||||
-- Take a copy of the privilege table
|
-- Take a copy of the privilege table
|
||||||
local privileges = {}
|
local privileges = {}
|
||||||
for priv, _ in pairs(auth_table[name].privileges) do
|
for priv, _ in pairs(auth_entry.privileges) do
|
||||||
privileges[priv] = true
|
privileges[priv] = true
|
||||||
end
|
end
|
||||||
-- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
|
-- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
|
||||||
@ -89,85 +39,89 @@ core.builtin_auth_handler = {
|
|||||||
end
|
end
|
||||||
-- All done
|
-- All done
|
||||||
return {
|
return {
|
||||||
password = auth_table[name].password,
|
password = auth_entry.password,
|
||||||
privileges = privileges,
|
privileges = privileges,
|
||||||
-- Is set to nil if unknown
|
-- Is set to nil if unknown
|
||||||
last_login = auth_table[name].last_login,
|
last_login = auth_entry.last_login,
|
||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
create_auth = function(name, password)
|
create_auth = function(name, password)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
assert(type(password) == "string")
|
assert(type(password) == "string")
|
||||||
core.log('info', "Built-in authentication handler adding player '"..name.."'")
|
core.log('info', "Built-in authentication handler adding player '"..name.."'")
|
||||||
auth_table[name] = {
|
return core_auth.create({
|
||||||
|
name = name,
|
||||||
password = password,
|
password = password,
|
||||||
privileges = core.string_to_privs(core.settings:get("default_privs")),
|
privileges = core.string_to_privs(core.settings:get("default_privs")),
|
||||||
last_login = os.time(),
|
last_login = os.time(),
|
||||||
}
|
})
|
||||||
save_auth_file()
|
|
||||||
end,
|
end,
|
||||||
delete_auth = function(name)
|
delete_auth = function(name)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
if not auth_table[name] then
|
local auth_entry = core_auth.read(name)
|
||||||
|
if not auth_entry then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
core.log('info', "Built-in authentication handler deleting player '"..name.."'")
|
core.log('info', "Built-in authentication handler deleting player '"..name.."'")
|
||||||
auth_table[name] = nil
|
return core_auth.delete(name)
|
||||||
save_auth_file()
|
|
||||||
return true
|
|
||||||
end,
|
end,
|
||||||
set_password = function(name, password)
|
set_password = function(name, password)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
assert(type(password) == "string")
|
assert(type(password) == "string")
|
||||||
if not auth_table[name] then
|
local auth_entry = core_auth.read(name)
|
||||||
|
if not auth_entry then
|
||||||
core.builtin_auth_handler.create_auth(name, password)
|
core.builtin_auth_handler.create_auth(name, password)
|
||||||
else
|
else
|
||||||
core.log('info', "Built-in authentication handler setting password of player '"..name.."'")
|
core.log('info', "Built-in authentication handler setting password of player '"..name.."'")
|
||||||
auth_table[name].password = password
|
auth_entry.password = password
|
||||||
save_auth_file()
|
core_auth.save(auth_entry)
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
set_privileges = function(name, privileges)
|
set_privileges = function(name, privileges)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
assert(type(privileges) == "table")
|
assert(type(privileges) == "table")
|
||||||
if not auth_table[name] then
|
local auth_entry = core_auth.read(name)
|
||||||
core.builtin_auth_handler.create_auth(name,
|
if not auth_entry then
|
||||||
|
auth_entry = core.builtin_auth_handler.create_auth(name,
|
||||||
core.get_password_hash(name,
|
core.get_password_hash(name,
|
||||||
core.settings:get("default_password")))
|
core.settings:get("default_password")))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run grant callbacks
|
-- Run grant callbacks
|
||||||
for priv, _ in pairs(privileges) do
|
for priv, _ in pairs(privileges) do
|
||||||
if not auth_table[name].privileges[priv] then
|
if not auth_entry.privileges[priv] then
|
||||||
core.run_priv_callbacks(name, priv, nil, "grant")
|
core.run_priv_callbacks(name, priv, nil, "grant")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run revoke callbacks
|
-- Run revoke callbacks
|
||||||
for priv, _ in pairs(auth_table[name].privileges) do
|
for priv, _ in pairs(auth_entry.privileges) do
|
||||||
if not privileges[priv] then
|
if not privileges[priv] then
|
||||||
core.run_priv_callbacks(name, priv, nil, "revoke")
|
core.run_priv_callbacks(name, priv, nil, "revoke")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
auth_table[name].privileges = privileges
|
auth_entry.privileges = privileges
|
||||||
|
core_auth.save(auth_entry)
|
||||||
core.notify_authentication_modified(name)
|
core.notify_authentication_modified(name)
|
||||||
save_auth_file()
|
|
||||||
end,
|
end,
|
||||||
reload = function()
|
reload = function()
|
||||||
read_auth_file()
|
core_auth.reload()
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
record_login = function(name)
|
record_login = function(name)
|
||||||
assert(type(name) == "string")
|
assert(type(name) == "string")
|
||||||
assert(auth_table[name]).last_login = os.time()
|
local auth_entry = core_auth.read(name)
|
||||||
save_auth_file()
|
assert(auth_entry)
|
||||||
|
auth_entry.last_login = os.time()
|
||||||
|
core_auth.save(auth_entry)
|
||||||
end,
|
end,
|
||||||
iterate = function()
|
iterate = function()
|
||||||
local names = {}
|
local names = {}
|
||||||
for k in pairs(auth_table) do
|
local nameslist = core_auth.list_names()
|
||||||
names[k] = true
|
for k,v in pairs(nameslist) do
|
||||||
|
names[v] = true
|
||||||
end
|
end
|
||||||
return pairs(names)
|
return pairs(names)
|
||||||
end,
|
end,
|
||||||
@ -177,12 +131,13 @@ core.register_on_prejoinplayer(function(name, ip)
|
|||||||
if core.registered_auth_handler ~= nil then
|
if core.registered_auth_handler ~= nil then
|
||||||
return -- Don't do anything if custom auth handler registered
|
return -- Don't do anything if custom auth handler registered
|
||||||
end
|
end
|
||||||
if auth_table[name] ~= nil then
|
local auth_entry = core_auth.read(name)
|
||||||
|
if auth_entry ~= nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local name_lower = name:lower()
|
local name_lower = name:lower()
|
||||||
for k in pairs(auth_table) do
|
for k in core.builtin_auth_handler.iterate() do
|
||||||
if k:lower() == name_lower then
|
if k:lower() == name_lower then
|
||||||
return string.format("\nCannot create new player called '%s'. "..
|
return string.format("\nCannot create new player called '%s'. "..
|
||||||
"Another account called '%s' is already registered. "..
|
"Another account called '%s' is already registered. "..
|
||||||
|
@ -29,6 +29,7 @@ It can be copied over from an old world to a newly created world.
|
|||||||
|
|
||||||
World
|
World
|
||||||
|-- auth.txt ----- Authentication data
|
|-- auth.txt ----- Authentication data
|
||||||
|
|-- auth.sqlite -- Authentication data (SQLite alternative)
|
||||||
|-- env_meta.txt - Environment metadata
|
|-- env_meta.txt - Environment metadata
|
||||||
|-- ipban.txt ---- Banned ips/users
|
|-- ipban.txt ---- Banned ips/users
|
||||||
|-- map_meta.txt - Map metadata
|
|-- map_meta.txt - Map metadata
|
||||||
@ -62,6 +63,34 @@ Example lines:
|
|||||||
- Player "bar", no password, no privileges:
|
- Player "bar", no password, no privileges:
|
||||||
bar::
|
bar::
|
||||||
|
|
||||||
|
auth.sqlite
|
||||||
|
------------
|
||||||
|
Contains authentification data as an SQLite database. This replaces auth.txt
|
||||||
|
above when auth_backend is set to "sqlite3" in world.mt .
|
||||||
|
|
||||||
|
This database contains two tables "auth" and "user_privileges":
|
||||||
|
|
||||||
|
CREATE TABLE `auth` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
`name` VARCHAR(32) UNIQUE,
|
||||||
|
`password` VARCHAR(512),
|
||||||
|
`last_login` INTEGER
|
||||||
|
);
|
||||||
|
CREATE TABLE `user_privileges` (
|
||||||
|
`id` INTEGER,
|
||||||
|
`privilege` VARCHAR(32),
|
||||||
|
PRIMARY KEY (id, privilege)
|
||||||
|
CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
The "name" and "password" fields of the auth table are the same as the auth.txt
|
||||||
|
fields (with modern password hash). The "last_login" field is the last login
|
||||||
|
time as a unix time stamp.
|
||||||
|
|
||||||
|
The "user_privileges" table contains one entry per privilege and player.
|
||||||
|
A player with "interact" and "shout" privileges will have two entries, one
|
||||||
|
with privilege="interact" and the second with privilege="shout".
|
||||||
|
|
||||||
env_meta.txt
|
env_meta.txt
|
||||||
-------------
|
-------------
|
||||||
Simple global environment variables.
|
Simple global environment variables.
|
||||||
@ -107,6 +136,7 @@ Example content (added indentation and - explanations):
|
|||||||
readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder)
|
readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder)
|
||||||
server_announce = false - whether the server is publicly announced or not
|
server_announce = false - whether the server is publicly announced or not
|
||||||
load_mod_<mod> = false - whether <mod> is to be loaded in this world
|
load_mod_<mod> = false - whether <mod> is to be loaded in this world
|
||||||
|
auth_backend = files - which DB backend to use for authentication data
|
||||||
|
|
||||||
Player File Format
|
Player File Format
|
||||||
===================
|
===================
|
||||||
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
#include "filesys.h"
|
#include "filesys.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
// !!! WARNING !!!
|
// !!! WARNING !!!
|
||||||
// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
|
// 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());
|
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;
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
// for player files
|
// for player files
|
||||||
|
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
class PlayerDatabaseFiles : public PlayerDatabase
|
class PlayerDatabaseFiles : public PlayerDatabase
|
||||||
{
|
{
|
||||||
@ -41,3 +42,23 @@ private:
|
|||||||
|
|
||||||
std::string m_savedir;
|
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();
|
||||||
|
};
|
||||||
|
@ -606,3 +606,170 @@ void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
|
|||||||
|
|
||||||
sqlite3_reset(m_stmt_player_list);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -85,6 +85,16 @@ protected:
|
|||||||
return (u32) sqlite3_column_int(s, iCol);
|
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)
|
inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
|
||||||
{
|
{
|
||||||
return (float) sqlite3_column_double(s, 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_remove = nullptr;
|
||||||
sqlite3_stmt *m_stmt_player_metadata_add = 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;
|
||||||
|
};
|
||||||
|
@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "irr_v3d.h"
|
#include "irr_v3d.h"
|
||||||
@ -61,3 +62,25 @@ public:
|
|||||||
virtual bool removePlayer(const std::string &name) = 0;
|
virtual bool removePlayer(const std::string &name) = 0;
|
||||||
virtual void listPlayers(std::vector<std::string> &res) = 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;
|
||||||
|
};
|
||||||
|
@ -289,6 +289,8 @@ static void set_allowed_options(OptionList *allowed_options)
|
|||||||
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
|
_("Migrate from current map backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING,
|
allowed_options->insert(std::make_pair("migrate-players", ValueSpec(VALUETYPE_STRING,
|
||||||
_("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
|
_("Migrate from current players backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
|
allowed_options->insert(std::make_pair("migrate-auth", ValueSpec(VALUETYPE_STRING,
|
||||||
|
_("Migrate from current auth backend to another (Only works when using minetestserver or with --server)"))));
|
||||||
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG,
|
||||||
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
_("Feature an interactive terminal (Only works when using minetestserver or with --server)"))));
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
@ -840,6 +842,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
|
|||||||
if (cmd_args.exists("migrate-players"))
|
if (cmd_args.exists("migrate-players"))
|
||||||
return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args);
|
return ServerEnvironment::migratePlayersDatabase(game_params, cmd_args);
|
||||||
|
|
||||||
|
if (cmd_args.exists("migrate-auth"))
|
||||||
|
return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args);
|
||||||
|
|
||||||
if (cmd_args.exists("terminal")) {
|
if (cmd_args.exists("terminal")) {
|
||||||
#if USE_CURSES
|
#if USE_CURSES
|
||||||
bool name_ok = true;
|
bool name_ok = true;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
set(common_SCRIPT_LUA_API_SRCS
|
set(common_SCRIPT_LUA_API_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_areastore.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_areastore.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/l_auth.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_base.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_base.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_craft.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_craft.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp
|
||||||
|
216
src/script/lua_api/l_auth.cpp
Normal file
216
src/script/lua_api/l_auth.cpp
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lua_api/l_auth.h"
|
||||||
|
#include "lua_api/l_internal.h"
|
||||||
|
#include "common/c_converter.h"
|
||||||
|
#include "common/c_content.h"
|
||||||
|
#include "cpp_api/s_base.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "environment.h"
|
||||||
|
#include "database/database.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// common start: ensure auth db
|
||||||
|
AuthDatabase *ModApiAuth::getAuthDb(lua_State *L)
|
||||||
|
{
|
||||||
|
ServerEnvironment *server_environment =
|
||||||
|
dynamic_cast<ServerEnvironment *>(getEnv(L));
|
||||||
|
if (!server_environment)
|
||||||
|
return nullptr;
|
||||||
|
return server_environment->getAuthDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModApiAuth::pushAuthEntry(lua_State *L, const AuthEntry &authEntry)
|
||||||
|
{
|
||||||
|
lua_newtable(L);
|
||||||
|
int table = lua_gettop(L);
|
||||||
|
// id
|
||||||
|
lua_pushnumber(L, authEntry.id);
|
||||||
|
lua_setfield(L, table, "id");
|
||||||
|
// name
|
||||||
|
lua_pushstring(L, authEntry.name.c_str());
|
||||||
|
lua_setfield(L, table, "name");
|
||||||
|
// password
|
||||||
|
lua_pushstring(L, authEntry.password.c_str());
|
||||||
|
lua_setfield(L, table, "password");
|
||||||
|
// privileges
|
||||||
|
lua_newtable(L);
|
||||||
|
int privtable = lua_gettop(L);
|
||||||
|
for (const std::string &privs : authEntry.privileges) {
|
||||||
|
lua_pushboolean(L, true);
|
||||||
|
lua_setfield(L, privtable, privs.c_str());
|
||||||
|
}
|
||||||
|
lua_setfield(L, table, "privileges");
|
||||||
|
// last_login
|
||||||
|
lua_pushnumber(L, authEntry.last_login);
|
||||||
|
lua_setfield(L, table, "last_login");
|
||||||
|
|
||||||
|
lua_pushvalue(L, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_read(name)
|
||||||
|
int ModApiAuth::l_auth_read(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (!auth_db)
|
||||||
|
return 0;
|
||||||
|
AuthEntry authEntry;
|
||||||
|
const char *name = luaL_checkstring(L, 1);
|
||||||
|
bool success = auth_db->getAuth(std::string(name), authEntry);
|
||||||
|
if (!success)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pushAuthEntry(L, authEntry);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_save(table)
|
||||||
|
int ModApiAuth::l_auth_save(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (!auth_db)
|
||||||
|
return 0;
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
int table = 1;
|
||||||
|
AuthEntry authEntry;
|
||||||
|
bool success;
|
||||||
|
success = getintfield(L, table, "id", authEntry.id);
|
||||||
|
success = success && getstringfield(L, table, "name", authEntry.name);
|
||||||
|
success = success && getstringfield(L, table, "password", authEntry.password);
|
||||||
|
lua_getfield(L, table, "privileges");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, -2)) {
|
||||||
|
authEntry.privileges.emplace_back(
|
||||||
|
lua_tostring(L, -2)); // the key, not the value
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // the table
|
||||||
|
success = success && getintfield(L, table, "last_login", authEntry.last_login);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
lua_pushboolean(L, false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(L, auth_db->saveAuth(authEntry));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_create(table)
|
||||||
|
int ModApiAuth::l_auth_create(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (!auth_db)
|
||||||
|
return 0;
|
||||||
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
|
int table = 1;
|
||||||
|
AuthEntry authEntry;
|
||||||
|
bool success;
|
||||||
|
// no meaningful id field, we assume
|
||||||
|
success = getstringfield(L, table, "name", authEntry.name);
|
||||||
|
success = success && getstringfield(L, table, "password", authEntry.password);
|
||||||
|
lua_getfield(L, table, "privileges");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
while (lua_next(L, -2)) {
|
||||||
|
authEntry.privileges.emplace_back(
|
||||||
|
lua_tostring(L, -2)); // the key, not the value
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // the table
|
||||||
|
success = success && getintfield(L, table, "last_login", authEntry.last_login);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (auth_db->createAuth(authEntry)) {
|
||||||
|
pushAuthEntry(L, authEntry);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_delete(name)
|
||||||
|
int ModApiAuth::l_auth_delete(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (!auth_db)
|
||||||
|
return 0;
|
||||||
|
std::string name(luaL_checkstring(L, 1));
|
||||||
|
lua_pushboolean(L, auth_db->deleteAuth(name));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_list_names()
|
||||||
|
int ModApiAuth::l_auth_list_names(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (!auth_db)
|
||||||
|
return 0;
|
||||||
|
std::vector<std::string> names;
|
||||||
|
auth_db->listNames(names);
|
||||||
|
lua_createtable(L, names.size(), 0);
|
||||||
|
int table = lua_gettop(L);
|
||||||
|
int i = 1;
|
||||||
|
for (const std::string &name : names) {
|
||||||
|
lua_pushstring(L, name.c_str());
|
||||||
|
lua_rawseti(L, table, i++);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth_reload()
|
||||||
|
int ModApiAuth::l_auth_reload(lua_State *L)
|
||||||
|
{
|
||||||
|
NO_MAP_LOCK_REQUIRED;
|
||||||
|
AuthDatabase *auth_db = getAuthDb(L);
|
||||||
|
if (auth_db)
|
||||||
|
auth_db->reload();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModApiAuth::Initialize(lua_State *L, int top)
|
||||||
|
{
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
int auth_top = lua_gettop(L);
|
||||||
|
|
||||||
|
registerFunction(L, "read", l_auth_read, auth_top);
|
||||||
|
registerFunction(L, "save", l_auth_save, auth_top);
|
||||||
|
registerFunction(L, "create", l_auth_create, auth_top);
|
||||||
|
registerFunction(L, "delete", l_auth_delete, auth_top);
|
||||||
|
registerFunction(L, "list_names", l_auth_list_names, auth_top);
|
||||||
|
registerFunction(L, "reload", l_auth_reload, auth_top);
|
||||||
|
|
||||||
|
lua_setfield(L, top, "auth");
|
||||||
|
}
|
54
src/script/lua_api/l_auth.h
Normal file
54
src/script/lua_api/l_auth.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua_api/l_base.h"
|
||||||
|
|
||||||
|
class AuthDatabase;
|
||||||
|
struct AuthEntry;
|
||||||
|
|
||||||
|
class ModApiAuth : public ModApiBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// auth_read(name)
|
||||||
|
static int l_auth_read(lua_State *L);
|
||||||
|
|
||||||
|
// auth_save(table)
|
||||||
|
static int l_auth_save(lua_State *L);
|
||||||
|
|
||||||
|
// auth_create(table)
|
||||||
|
static int l_auth_create(lua_State *L);
|
||||||
|
|
||||||
|
// auth_delete(name)
|
||||||
|
static int l_auth_delete(lua_State *L);
|
||||||
|
|
||||||
|
// auth_list_names()
|
||||||
|
static int l_auth_list_names(lua_State *L);
|
||||||
|
|
||||||
|
// auth_reload()
|
||||||
|
static int l_auth_reload(lua_State *L);
|
||||||
|
|
||||||
|
// helper for auth* methods
|
||||||
|
static AuthDatabase *getAuthDb(lua_State *L);
|
||||||
|
static void pushAuthEntry(lua_State *L, const AuthEntry &authEntry);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void Initialize(lua_State *L, int top);
|
||||||
|
};
|
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "cpp_api/s_internal.h"
|
#include "cpp_api/s_internal.h"
|
||||||
#include "lua_api/l_areastore.h"
|
#include "lua_api/l_areastore.h"
|
||||||
|
#include "lua_api/l_auth.h"
|
||||||
#include "lua_api/l_base.h"
|
#include "lua_api/l_base.h"
|
||||||
#include "lua_api/l_craft.h"
|
#include "lua_api/l_craft.h"
|
||||||
#include "lua_api/l_env.h"
|
#include "lua_api/l_env.h"
|
||||||
@ -106,6 +107,7 @@ void ServerScripting::InitializeModApi(lua_State *L, int top)
|
|||||||
ModChannelRef::Register(L);
|
ModChannelRef::Register(L);
|
||||||
|
|
||||||
// Initialize mod api modules
|
// Initialize mod api modules
|
||||||
|
ModApiAuth::Initialize(L, top);
|
||||||
ModApiCraft::Initialize(L, top);
|
ModApiCraft::Initialize(L, top);
|
||||||
ModApiEnvMod::Initialize(L, top);
|
ModApiEnvMod::Initialize(L, top);
|
||||||
ModApiInventory::Initialize(L, top);
|
ModApiInventory::Initialize(L, top);
|
||||||
|
@ -414,6 +414,18 @@ ServerEnvironment::ServerEnvironment(ServerMap *map,
|
|||||||
std::string name;
|
std::string name;
|
||||||
conf.getNoEx("player_backend", name);
|
conf.getNoEx("player_backend", name);
|
||||||
m_player_database = openPlayerDatabase(name, path_world, conf);
|
m_player_database = openPlayerDatabase(name, path_world, conf);
|
||||||
|
|
||||||
|
std::string auth_name = "files";
|
||||||
|
if (conf.exists("auth_backend")) {
|
||||||
|
conf.getNoEx("auth_backend", auth_name);
|
||||||
|
} else {
|
||||||
|
conf.set("auth_backend", "files");
|
||||||
|
if (!conf.updateConfigFile(conf_path.c_str())) {
|
||||||
|
errorstream << "ServerEnvironment::ServerEnvironment(): "
|
||||||
|
<< "Failed to update world.mt!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_auth_database = openAuthDatabase(auth_name, path_world, conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerEnvironment::~ServerEnvironment()
|
ServerEnvironment::~ServerEnvironment()
|
||||||
@ -439,6 +451,7 @@ ServerEnvironment::~ServerEnvironment()
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete m_player_database;
|
delete m_player_database;
|
||||||
|
delete m_auth_database;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map & ServerEnvironment::getMap()
|
Map & ServerEnvironment::getMap()
|
||||||
@ -2274,3 +2287,91 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthDatabase *ServerEnvironment::openAuthDatabase(
|
||||||
|
const std::string &name, const std::string &savedir, const Settings &conf)
|
||||||
|
{
|
||||||
|
if (name == "sqlite3")
|
||||||
|
return new AuthDatabaseSQLite3(savedir);
|
||||||
|
|
||||||
|
if (name == "files")
|
||||||
|
return new AuthDatabaseFiles(savedir);
|
||||||
|
|
||||||
|
throw BaseException(std::string("Database backend ") + name + " not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerEnvironment::migrateAuthDatabase(
|
||||||
|
const GameParams &game_params, const Settings &cmd_args)
|
||||||
|
{
|
||||||
|
std::string migrate_to = cmd_args.get("migrate-auth");
|
||||||
|
Settings world_mt;
|
||||||
|
std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
|
||||||
|
if (!world_mt.readConfigFile(world_mt_path.c_str())) {
|
||||||
|
errorstream << "Cannot read world.mt!" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string backend = "files";
|
||||||
|
if (world_mt.exists("auth_backend"))
|
||||||
|
backend = world_mt.get("auth_backend");
|
||||||
|
else
|
||||||
|
warningstream << "No auth_backend found in world.mt, "
|
||||||
|
"assuming \"files\"." << std::endl;
|
||||||
|
|
||||||
|
if (backend == migrate_to) {
|
||||||
|
errorstream << "Cannot migrate: new backend is same"
|
||||||
|
<< " as the old one" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
|
||||||
|
backend, game_params.world_path, world_mt));
|
||||||
|
const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
|
||||||
|
migrate_to, game_params.world_path, world_mt));
|
||||||
|
|
||||||
|
std::vector<std::string> names_list;
|
||||||
|
srcdb->listNames(names_list);
|
||||||
|
for (const std::string &name : names_list) {
|
||||||
|
actionstream << "Migrating auth entry for " << name << std::endl;
|
||||||
|
bool success;
|
||||||
|
AuthEntry authEntry;
|
||||||
|
success = srcdb->getAuth(name, authEntry);
|
||||||
|
success = success && dstdb->createAuth(authEntry);
|
||||||
|
if (!success)
|
||||||
|
errorstream << "Failed to migrate " << name << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionstream << "Successfully migrated " << names_list.size()
|
||||||
|
<< " auth entries" << std::endl;
|
||||||
|
world_mt.set("auth_backend", migrate_to);
|
||||||
|
if (!world_mt.updateConfigFile(world_mt_path.c_str()))
|
||||||
|
errorstream << "Failed to update world.mt!" << std::endl;
|
||||||
|
else
|
||||||
|
actionstream << "world.mt updated" << std::endl;
|
||||||
|
|
||||||
|
if (backend == "files") {
|
||||||
|
// special-case files migration:
|
||||||
|
// move auth.txt to auth.txt.bak if possible
|
||||||
|
std::string auth_txt_path =
|
||||||
|
game_params.world_path + DIR_DELIM + "auth.txt";
|
||||||
|
std::string auth_bak_path = auth_txt_path + ".bak";
|
||||||
|
if (!fs::PathExists(auth_bak_path))
|
||||||
|
if (fs::Rename(auth_txt_path, auth_bak_path))
|
||||||
|
actionstream << "Renamed auth.txt to auth.txt.bak"
|
||||||
|
<< std::endl;
|
||||||
|
else
|
||||||
|
errorstream << "Could not rename auth.txt to "
|
||||||
|
"auth.txt.bak" << std::endl;
|
||||||
|
else
|
||||||
|
warningstream << "auth.txt.bak already exists, auth.txt "
|
||||||
|
"not renamed" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BaseException &e) {
|
||||||
|
errorstream << "An error occured during migration: " << e.what()
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -32,6 +32,7 @@ struct GameParams;
|
|||||||
class MapBlock;
|
class MapBlock;
|
||||||
class RemotePlayer;
|
class RemotePlayer;
|
||||||
class PlayerDatabase;
|
class PlayerDatabase;
|
||||||
|
class AuthDatabase;
|
||||||
class PlayerSAO;
|
class PlayerSAO;
|
||||||
class ServerEnvironment;
|
class ServerEnvironment;
|
||||||
class ActiveBlockModifier;
|
class ActiveBlockModifier;
|
||||||
@ -366,6 +367,10 @@ public:
|
|||||||
|
|
||||||
static bool migratePlayersDatabase(const GameParams &game_params,
|
static bool migratePlayersDatabase(const GameParams &game_params,
|
||||||
const Settings &cmd_args);
|
const Settings &cmd_args);
|
||||||
|
|
||||||
|
AuthDatabase *getAuthDatabase() { return m_auth_database; }
|
||||||
|
static bool migrateAuthDatabase(const GameParams &game_params,
|
||||||
|
const Settings &cmd_args);
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -375,6 +380,8 @@ private:
|
|||||||
|
|
||||||
static PlayerDatabase *openPlayerDatabase(const std::string &name,
|
static PlayerDatabase *openPlayerDatabase(const std::string &name,
|
||||||
const std::string &savedir, const Settings &conf);
|
const std::string &savedir, const Settings &conf);
|
||||||
|
static AuthDatabase *openAuthDatabase(const std::string &name,
|
||||||
|
const std::string &savedir, const Settings &conf);
|
||||||
/*
|
/*
|
||||||
Internal ActiveObject interface
|
Internal ActiveObject interface
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
@ -467,6 +474,7 @@ private:
|
|||||||
std::vector<RemotePlayer*> m_players;
|
std::vector<RemotePlayer*> m_players;
|
||||||
|
|
||||||
PlayerDatabase *m_player_database = nullptr;
|
PlayerDatabase *m_player_database = nullptr;
|
||||||
|
AuthDatabase *m_auth_database = nullptr;
|
||||||
|
|
||||||
// Particles
|
// Particles
|
||||||
IntervalLimiter m_particle_management_interval;
|
IntervalLimiter m_particle_management_interval;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
set (UNITTEST_SRCS
|
set (UNITTEST_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_authdatabase.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_activeobject.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_activeobject.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_areastore.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_areastore.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_ban.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_ban.cpp
|
||||||
|
299
src/unittest/test_authdatabase.cpp
Normal file
299
src/unittest/test_authdatabase.cpp
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "database/database-files.h"
|
||||||
|
#include "database/database-sqlite3.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Anonymous namespace to create classes that are only
|
||||||
|
// visible to this file
|
||||||
|
//
|
||||||
|
// These are helpers that return a *AuthDatabase and
|
||||||
|
// allow us to run the same tests on different databases and
|
||||||
|
// database acquisition strategies.
|
||||||
|
|
||||||
|
class AuthDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~AuthDatabaseProvider() = default;
|
||||||
|
virtual AuthDatabase *getAuthDatabase() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FixedProvider : public AuthDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FixedProvider(AuthDatabase *auth_db) : auth_db(auth_db){};
|
||||||
|
virtual ~FixedProvider(){};
|
||||||
|
virtual AuthDatabase *getAuthDatabase() { return auth_db; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
AuthDatabase *auth_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FilesProvider : public AuthDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FilesProvider(const std::string &dir) : dir(dir){};
|
||||||
|
virtual ~FilesProvider() { delete auth_db; };
|
||||||
|
virtual AuthDatabase *getAuthDatabase()
|
||||||
|
{
|
||||||
|
delete auth_db;
|
||||||
|
auth_db = new AuthDatabaseFiles(dir);
|
||||||
|
return auth_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string dir;
|
||||||
|
AuthDatabase *auth_db = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SQLite3Provider : public AuthDatabaseProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SQLite3Provider(const std::string &dir) : dir(dir){};
|
||||||
|
virtual ~SQLite3Provider() { delete auth_db; };
|
||||||
|
virtual AuthDatabase *getAuthDatabase()
|
||||||
|
{
|
||||||
|
delete auth_db;
|
||||||
|
auth_db = new AuthDatabaseSQLite3(dir);
|
||||||
|
return auth_db;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string dir;
|
||||||
|
AuthDatabase *auth_db = nullptr;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestAuthDatabase : public TestBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TestAuthDatabase()
|
||||||
|
{
|
||||||
|
TestManager::registerTestModule(this);
|
||||||
|
// fixed directory, for persistence
|
||||||
|
test_dir = getTestTempDirectory();
|
||||||
|
}
|
||||||
|
const char *getName() { return "TestAuthDatabase"; }
|
||||||
|
|
||||||
|
void runTests(IGameDef *gamedef);
|
||||||
|
void runTestsForCurrentDB();
|
||||||
|
|
||||||
|
void testRecallFail();
|
||||||
|
void testCreate();
|
||||||
|
void testRecall();
|
||||||
|
void testChange();
|
||||||
|
void testRecallChanged();
|
||||||
|
void testChangePrivileges();
|
||||||
|
void testRecallChangedPrivileges();
|
||||||
|
void testListNames();
|
||||||
|
void testDelete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string test_dir;
|
||||||
|
AuthDatabaseProvider *auth_provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TestAuthDatabase g_test_instance;
|
||||||
|
|
||||||
|
void TestAuthDatabase::runTests(IGameDef *gamedef)
|
||||||
|
{
|
||||||
|
// Each set of tests is run twice for each database type:
|
||||||
|
// one where we reuse the same AuthDatabase object (to test local caching),
|
||||||
|
// and one where we create a new AuthDatabase object for each call
|
||||||
|
// (to test actual persistence).
|
||||||
|
|
||||||
|
rawstream << "-------- Files database (same object)" << std::endl;
|
||||||
|
|
||||||
|
AuthDatabase *auth_db = new AuthDatabaseFiles(test_dir);
|
||||||
|
auth_provider = new FixedProvider(auth_db);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete auth_db;
|
||||||
|
delete auth_provider;
|
||||||
|
|
||||||
|
// reset database
|
||||||
|
fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "auth.txt");
|
||||||
|
|
||||||
|
rawstream << "-------- Files database (new objects)" << std::endl;
|
||||||
|
|
||||||
|
auth_provider = new FilesProvider(test_dir);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete auth_provider;
|
||||||
|
|
||||||
|
rawstream << "-------- SQLite3 database (same object)" << std::endl;
|
||||||
|
|
||||||
|
auth_db = new AuthDatabaseSQLite3(test_dir);
|
||||||
|
auth_provider = new FixedProvider(auth_db);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete auth_db;
|
||||||
|
delete auth_provider;
|
||||||
|
|
||||||
|
// reset database
|
||||||
|
fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "auth.sqlite");
|
||||||
|
|
||||||
|
rawstream << "-------- SQLite3 database (new objects)" << std::endl;
|
||||||
|
|
||||||
|
auth_provider = new SQLite3Provider(test_dir);
|
||||||
|
|
||||||
|
runTestsForCurrentDB();
|
||||||
|
|
||||||
|
delete auth_provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void TestAuthDatabase::runTestsForCurrentDB()
|
||||||
|
{
|
||||||
|
TEST(testRecallFail);
|
||||||
|
TEST(testCreate);
|
||||||
|
TEST(testRecall);
|
||||||
|
TEST(testChange);
|
||||||
|
TEST(testRecallChanged);
|
||||||
|
TEST(testChangePrivileges);
|
||||||
|
TEST(testRecallChangedPrivileges);
|
||||||
|
TEST(testListNames);
|
||||||
|
TEST(testDelete);
|
||||||
|
TEST(testRecallFail);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testRecallFail()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
// no such user yet
|
||||||
|
UASSERT(!auth_db->getAuth("TestName", authEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testCreate()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
authEntry.name = "TestName";
|
||||||
|
authEntry.password = "TestPassword";
|
||||||
|
authEntry.privileges.emplace_back("shout");
|
||||||
|
authEntry.privileges.emplace_back("interact");
|
||||||
|
authEntry.last_login = 1000;
|
||||||
|
UASSERT(auth_db->createAuth(authEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testRecall()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
UASSERT(auth_db->getAuth("TestName", authEntry));
|
||||||
|
UASSERTEQ(std::string, authEntry.name, "TestName");
|
||||||
|
UASSERTEQ(std::string, authEntry.password, "TestPassword");
|
||||||
|
// the order of privileges is unimportant
|
||||||
|
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
|
||||||
|
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "interact,shout");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testChange()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
UASSERT(auth_db->getAuth("TestName", authEntry));
|
||||||
|
authEntry.password = "NewPassword";
|
||||||
|
authEntry.last_login = 1002;
|
||||||
|
UASSERT(auth_db->saveAuth(authEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testRecallChanged()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
UASSERT(auth_db->getAuth("TestName", authEntry));
|
||||||
|
UASSERTEQ(std::string, authEntry.password, "NewPassword");
|
||||||
|
// the order of privileges is unimportant
|
||||||
|
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
|
||||||
|
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "interact,shout");
|
||||||
|
UASSERTEQ(u64, authEntry.last_login, 1002);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testChangePrivileges()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
UASSERT(auth_db->getAuth("TestName", authEntry));
|
||||||
|
authEntry.privileges.clear();
|
||||||
|
authEntry.privileges.emplace_back("interact");
|
||||||
|
authEntry.privileges.emplace_back("fly");
|
||||||
|
authEntry.privileges.emplace_back("dig");
|
||||||
|
UASSERT(auth_db->saveAuth(authEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testRecallChangedPrivileges()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
UASSERT(auth_db->getAuth("TestName", authEntry));
|
||||||
|
// the order of privileges is unimportant
|
||||||
|
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
|
||||||
|
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "dig,fly,interact");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testListNames()
|
||||||
|
{
|
||||||
|
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
std::vector<std::string> list;
|
||||||
|
|
||||||
|
AuthEntry authEntry;
|
||||||
|
|
||||||
|
authEntry.name = "SecondName";
|
||||||
|
authEntry.password = "SecondPassword";
|
||||||
|
authEntry.privileges.emplace_back("shout");
|
||||||
|
authEntry.privileges.emplace_back("interact");
|
||||||
|
authEntry.last_login = 1003;
|
||||||
|
auth_db->createAuth(authEntry);
|
||||||
|
|
||||||
|
auth_db->listNames(list);
|
||||||
|
// not necessarily sorted, so sort before comparing
|
||||||
|
std::sort(list.begin(), list.end());
|
||||||
|
UASSERTEQ(std::string, str_join(list, ","), "SecondName,TestName");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAuthDatabase::testDelete()
|
||||||
|
{
|
||||||
|
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
|
||||||
|
|
||||||
|
UASSERT(!auth_db->deleteAuth("NoSuchName"));
|
||||||
|
UASSERT(auth_db->deleteAuth("TestName"));
|
||||||
|
// second try, expect failure
|
||||||
|
UASSERT(!auth_db->deleteAuth("TestName"));
|
||||||
|
}
|
@ -51,6 +51,7 @@ public:
|
|||||||
void testIsNumber();
|
void testIsNumber();
|
||||||
void testIsPowerOfTwo();
|
void testIsPowerOfTwo();
|
||||||
void testMyround();
|
void testMyround();
|
||||||
|
void testStringJoin();
|
||||||
};
|
};
|
||||||
|
|
||||||
static TestUtilities g_test_instance;
|
static TestUtilities g_test_instance;
|
||||||
@ -78,6 +79,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
|
|||||||
TEST(testIsNumber);
|
TEST(testIsNumber);
|
||||||
TEST(testIsPowerOfTwo);
|
TEST(testIsPowerOfTwo);
|
||||||
TEST(testMyround);
|
TEST(testMyround);
|
||||||
|
TEST(testStringJoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -328,3 +330,24 @@ void TestUtilities::testMyround()
|
|||||||
UASSERT(myround(-6.5f) == -7);
|
UASSERT(myround(-6.5f) == -7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestUtilities::testStringJoin()
|
||||||
|
{
|
||||||
|
std::vector<std::string> input;
|
||||||
|
UASSERT(str_join(input, ",") == "");
|
||||||
|
|
||||||
|
input.emplace_back("one");
|
||||||
|
UASSERT(str_join(input, ",") == "one");
|
||||||
|
|
||||||
|
input.emplace_back("two");
|
||||||
|
UASSERT(str_join(input, ",") == "one,two");
|
||||||
|
|
||||||
|
input.emplace_back("three");
|
||||||
|
UASSERT(str_join(input, ",") == "one,two,three");
|
||||||
|
|
||||||
|
input[1] = "";
|
||||||
|
UASSERT(str_join(input, ",") == "one,,three");
|
||||||
|
|
||||||
|
input[1] = "two";
|
||||||
|
UASSERT(str_join(input, " and ") == "one and two and three");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -704,3 +704,22 @@ inline const std::string duration_to_string(int sec)
|
|||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins a vector of strings by the string \p delimiter.
|
||||||
|
*
|
||||||
|
* @return A std::string
|
||||||
|
*/
|
||||||
|
inline std::string str_join(const std::vector<std::string> &list,
|
||||||
|
const std::string &delimiter)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
bool first = true;
|
||||||
|
for (const auto &part : list) {
|
||||||
|
if (!first)
|
||||||
|
oss << delimiter;
|
||||||
|
oss << part;
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user