The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
forum_user_handler.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by Thomas Baumhauer <thomas.baumhauer@NOSPAMgmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#ifdef HAVE_MYSQLPP
17
21#include "hash.hpp"
22#include "log.hpp"
23#include "config.hpp"
24
25#include <boost/asio/post.hpp>
26
27#include <cstdlib>
28#include <sstream>
29
30static lg::log_domain log_mp_user_handler("mp_user_handler");
31#define ERR_UH LOG_STREAM(err, log_mp_user_handler)
32#define WRN_UH LOG_STREAM(warn, log_mp_user_handler)
33#define LOG_UH LOG_STREAM(info, log_mp_user_handler)
34#define DBG_UH LOG_STREAM(debug, log_mp_user_handler)
35
36namespace {
37 const int USER_INACTIVE = 1;
38 const int USER_IGNORE = 2;
39}
40
41fuh::fuh(const config& c)
42 : conn_(c)
43 , db_users_table_(c["db_users_table"].str())
44 , db_extra_table_(c["db_extra_table"].str())
45 , mp_mod_group_(0)
46 , site_admin_group_(0)
47 , forum_admin_group_(0)
48{
49 try {
50 mp_mod_group_ = std::stoi(c["mp_mod_group"].str());
51 } catch(...) {
52 ERR_UH << "Failed to convert the mp_mod_group value of '" << c["mp_mod_group"].str() << "' into an int! Defaulting to " << mp_mod_group_ << ".";
53 }
54 try {
55 site_admin_group_ = std::stoi(c["site_admin_group"].str());
56 } catch(...) {
57 ERR_UH << "Failed to convert the site_admin_group_ value of '" << c["site_admin_group"].str() << "' into an int! Defaulting to " << site_admin_group_ << ".";
58 }
59 try {
60 forum_admin_group_ = std::stoi(c["forum_admin_group"].str());
61 } catch(...) {
62 ERR_UH << "Failed to convert the forum_admin_group_ value of '" << c["forum_admin_group"].str() << "' into an int! Defaulting to " << forum_admin_group_ << ".";
63 }
64}
65
66bool fuh::login(const std::string& name, const std::string& password) {
67 // Retrieve users' password as hash
68 try {
69 std::string hash = get_hashed_password_from_db(name);
70
71 if(utils::md5::is_valid_hash(hash) || utils::bcrypt::is_valid_prefix(hash)) { // md5 hash
72 return password == hash;
73 } else {
74 ERR_UH << "Invalid hash for user '" << name << "'";
75 return false;
76 }
77 } catch (const error& e) {
78 ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message;
79 return false;
80 }
81}
82
83std::string fuh::extract_salt(const std::string& name) {
84
85 // Some double security, this should never be needed
86 if(!(user_exists(name))) {
87 return "";
88 }
89
90 std::string hash;
91
92 try {
93 hash = get_hashed_password_from_db(name);
94 } catch (const error& e) {
95 ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message;
96 return "";
97 }
98
100 return hash.substr(0,12);
101
103 try {
105 } catch(const utils::hash_error& err) {
106 ERR_UH << "Error getting salt from hash of user '" << name << "': " << err.what();
107 return "";
108 }
109 }
110
111 return "";
112}
113
114void fuh::user_logged_in(const std::string& name) {
115 auto now = chrono::serialize_timestamp(std::chrono::system_clock::now());
116 conn_.write_user_int("user_lastvisit", name, static_cast<int>(now));
117}
118
119bool fuh::user_exists(const std::string& name) {
120 return conn_.user_exists(name);
121}
122
123long fuh::get_forum_id(const std::string& name) {
124 return conn_.get_forum_id(name);
125}
126
127bool fuh::user_is_active(const std::string& name) {
128 int user_type = conn_.get_user_int(db_users_table_, "user_type", name);
129 return user_type != USER_INACTIVE && user_type != USER_IGNORE;
130}
131
132bool fuh::user_is_moderator(const std::string& name) {
133 if(!user_exists(name)){
134 return false;
135 }
136 return conn_.get_user_int(db_extra_table_, "user_is_moderator", name) == 1 || (mp_mod_group_ != 0 && conn_.is_user_in_groups(name, { mp_mod_group_ }));
137}
138
139void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
140 if(!user_exists(name)){
141 return;
142 }
143 conn_.write_user_int("user_is_moderator", name, is_moderator);
144}
145
146fuh::ban_info fuh::user_is_banned(const std::string& name, const std::string& addr)
147{
148 config b = conn_.get_ban_info(name, addr);
149
150 std::chrono::seconds ban_duration(0);
151 if(b["ban_end"].to_unsigned() != 0) {
152 auto time_remaining = chrono::parse_timestamp(b["ban_end"].to_unsigned()) - std::chrono::system_clock::now();
153 ban_duration = std::chrono::duration_cast<std::chrono::seconds>(time_remaining);
154 }
155
156 switch(b["ban_type"].to_int())
157 {
158 case BAN_NONE:
159 return {};
160 case BAN_IP:
161 LOG_UH << "User '" << name << "' ip " << addr << " banned by IP address";
162 return { BAN_IP, ban_duration };
163 case BAN_USER:
164 LOG_UH << "User '" << name << "' uid " << b["user_id"].str() << " banned by uid";
165 return { BAN_USER, ban_duration };
166 case BAN_EMAIL:
167 LOG_UH << "User '" << name << "' email " << b["email"].str() << " banned by email address";
168 return { BAN_EMAIL, ban_duration };
169 default:
170 ERR_UH << "Invalid ban type '" << b["ban_type"].to_int() << "' returned for user '" << name << "'";
171 return {};
172 }
173}
174
175std::string fuh::user_info(const std::string& name) {
176 if(!user_exists(name)) {
177 throw error("No user with the name '" + name + "' exists.");
178 }
179
180 auto reg_date = get_registrationdate(name);
181 auto ll_date = get_lastlogin(name);
182
183 static constexpr std::string_view format = "%a %b %d %T %Y"; // equivalent to std::ctime
184 std::string reg_string = chrono::format_local_timestamp(reg_date, format);
185 std::string ll_string;
186
187 if(ll_date > decltype(ll_date){}) {
188 ll_string = chrono::format_local_timestamp(ll_date, format);
189 } else {
190 ll_string = "Never\n";
191 }
192
193 std::stringstream info;
194 info << "Name: " << name << "\n"
195 << "Registered: " << reg_string
196 << "Last login: " << ll_string;
197 if(!user_is_active(name)) {
198 info << "This account is currently inactive.\n";
199 }
200
201 return info.str();
202}
203
204std::string fuh::get_hashed_password_from_db(const std::string& user) {
205 return conn_.get_user_string(db_users_table_, "user_password", user);
206}
207
208std::string fuh::get_user_email(const std::string& user) {
209 return conn_.get_user_string(db_users_table_, "user_email", user);
210}
211
212void fuh::db_update_addon_download_count(const std::string& instance_version, const std::string& id, const std::string& version) {
213 return conn_.update_addon_download_count(instance_version, id, version);
214}
215
216std::chrono::system_clock::time_point fuh::get_lastlogin(const std::string& user) {
217 return chrono::parse_timestamp(conn_.get_user_int(db_extra_table_, "user_lastvisit", user));
218}
219
220std::chrono::system_clock::time_point fuh::get_registrationdate(const std::string& user) {
221 return chrono::parse_timestamp(conn_.get_user_int(db_users_table_, "user_regdate", user));
222}
223
224std::string fuh::get_uuid(){
225 return conn_.get_uuid();
226}
227
228std::string fuh::get_tournaments(){
229 return conn_.get_tournaments();
230}
231
232void fuh::async_get_and_send_game_history(boost::asio::io_context& io_service, wesnothd::server& s, any_socket_ptr socket, int player_id, int offset, std::string& search_game_name, int search_content_type, std::string& search_content) {
233 boost::asio::post([this, &s, socket, player_id, offset, &io_service, search_game_name, search_content_type, search_content] {
234 boost::asio::post(io_service, [socket, &s, doc = conn_.get_game_history(player_id, offset, search_game_name, search_content_type, search_content)]{
235 s.send_to_player(socket, *doc);
236 });
237 });
238}
239
240void fuh::db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name, int reload, int observers, int is_public, int has_password){
241 conn_.insert_game_info(uuid, game_id, version, name, reload, observers, is_public, has_password);
242}
243
244void fuh::db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location){
245 conn_.update_game_end(uuid, game_id, replay_location);
246}
247
248void fuh::db_insert_game_player_info(const std::string& uuid, int game_id, const std::string& username, int side_number, int is_host, const std::string& faction, const std::string& version, const std::string& source, const std::string& current_user, const std::string& leaders){
249 conn_.insert_game_player_info(uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders);
250}
251
252unsigned long long fuh::db_insert_game_content_info(const std::string& uuid, int game_id, const std::string& type, const std::string& name, const std::string& id, const std::string& addon_id, const std::string& addon_version){
253 return conn_.insert_game_content_info(uuid, game_id, type, name, id, addon_id, addon_version);
254}
255
256void fuh::db_set_oos_flag(const std::string& uuid, int game_id){
257 conn_.set_oos_flag(uuid, game_id);
258}
259
260void fuh::async_test_query(boost::asio::io_context& io_service, int limit) {
261 boost::asio::post([this, limit, &io_service] {
262 ERR_UH << "async test query starts!";
263 int i = conn_.async_test_query(limit);
264 boost::asio::post(io_service, [i]{ ERR_UH << "async test query output: " << i; });
265 });
266}
267
268bool fuh::db_topic_id_exists(int topic_id) {
269 return conn_.topic_id_exists(topic_id);
270}
271
272void fuh::db_insert_addon_info(const std::string& instance_version, const std::string& id, const std::string& name, const std::string& type, const std::string& version, bool forum_auth, int topic_id, const std::string uploader) {
273 conn_.insert_addon_info(instance_version, id, name, type, version, forum_auth, topic_id, uploader);
274}
275
276unsigned long long fuh::db_insert_login(const std::string& username, const std::string& ip, const std::string& version) {
277 return conn_.insert_login(username, ip, version);
278}
279
280void fuh::db_update_logout(unsigned long long login_id) {
281 conn_.update_logout(login_id);
282}
283
284void fuh::get_users_for_ip(const std::string& ip, std::ostringstream* out) {
285 conn_.get_users_for_ip(ip, out);
286}
287
288void fuh::get_ips_for_user(const std::string& username, std::ostringstream* out) {
289 conn_.get_ips_for_user(username, out);
290}
291
292bool fuh::db_is_user_primary_author(const std::string& instance_version, const std::string& id, const std::string& username) {
293 return conn_.is_user_author(instance_version, id, username, 1);
294}
295
296bool fuh::db_is_user_secondary_author(const std::string& instance_version, const std::string& id, const std::string& username) {
297 return conn_.is_user_author(instance_version, id, username, 0);
298}
299
300void fuh::db_delete_addon_authors(const std::string& instance_version, const std::string& id) {
301 conn_.delete_addon_authors(instance_version, id);
302}
303
304void fuh::db_insert_addon_authors(const std::string& instance_version, const std::string& id, const std::vector<std::string>& primary_authors, const std::vector<std::string>& secondary_authors) {
305 // ignore any duplicate authors
306 std::set<std::string> inserted_authors;
307
308 for(const std::string& primary_author : primary_authors) {
309 if(inserted_authors.count(primary_author) == 0) {
310 inserted_authors.emplace(primary_author);
311 conn_.insert_addon_author(instance_version, id, primary_author, 1);
312 }
313 }
314 for(const std::string& secondary_author : secondary_authors) {
315 if(inserted_authors.count(secondary_author) == 0) {
316 inserted_authors.emplace(secondary_author);
317 conn_.insert_addon_author(instance_version, id, secondary_author, 0);
318 }
319 }
320}
321
322bool fuh::db_do_any_authors_exist(const std::string& instance_version, const std::string& id) {
323 return conn_.do_any_authors_exist(instance_version, id);
324}
325
326config fuh::db_get_addon_downloads_info(const std::string& instance_version, const std::string& id) {
327 return conn_.get_addon_downloads_info(instance_version, id);
328}
329
330config fuh::db_get_forum_auth_usage(const std::string& instance_version) {
331 return conn_.get_forum_auth_usage(instance_version);
332}
333
336}
337
338bool fuh::user_is_addon_admin(const std::string& name) {
340}
341
342#endif //HAVE_MYSQLPP
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void delete_addon_authors(const std::string &instance_version, const std::string &id)
void insert_addon_author(const std::string &instance_version, const std::string &id, const std::string &author, int is_primary)
bool do_any_authors_exist(const std::string &instance_version, const std::string &id)
void update_addon_download_count(const std::string &instance_version, const std::string &id, const std::string &version)
config get_addon_downloads_info(const std::string &instance_version, const std::string &id)
bool topic_id_exists(int topic_id)
bool is_user_in_groups(const std::string &name, const std::vector< int > &group_ids)
int async_test_query(int limit)
bool is_user_author(const std::string &instance_version, const std::string &id, const std::string &username, int is_primary)
long get_forum_id(const std::string &name)
config get_addon_admins(int site_admin_group, int forum_admin_group)
void get_ips_for_user(const std::string &username, std::ostringstream *out)
bool user_exists(const std::string &name)
void get_users_for_ip(const std::string &ip, std::ostringstream *out)
unsigned long long insert_login(const std::string &username, const std::string &ip, const std::string &version)
void update_logout(unsigned long long login_id)
void write_user_int(const std::string &column, const std::string &name, int value)
The provided value is updated if a row exists or a new row inserted otherwise.
std::unique_ptr< simple_wml::document > get_game_history(int player_id, int offset, std::string search_game_name, int search_content_type, std::string search_content)
This is an asynchronous query that is executed on a separate connection to retrieve the game history ...
config get_ban_info(const std::string &name, const std::string &ip)
void set_oos_flag(const std::string &uuid, int game_id)
void update_game_end(const std::string &uuid, int game_id, const std::string &replay_location)
int get_user_int(const std::string &table, const std::string &column, const std::string &name)
void insert_addon_info(const std::string &instance_version, const std::string &id, const std::string &name, const std::string &type, const std::string &version, bool forum_auth, int topic_id, const std::string &uploader)
std::string get_user_string(const std::string &table, const std::string &column, const std::string &name)
void insert_game_info(const std::string &uuid, int game_id, const std::string &version, const std::string &name, int reload, int observers, int is_public, int has_password)
config get_forum_auth_usage(const std::string &instance_version)
void insert_game_player_info(const std::string &uuid, int game_id, const std::string &username, int side_number, int is_host, const std::string &faction, const std::string &version, const std::string &source, const std::string &current_user, const std::string &leaders)
std::string get_uuid()
std::string get_tournaments()
unsigned long long insert_game_content_info(const std::string &uuid, int game_id, const std::string &type, const std::string &name, const std::string &id, const std::string &addon_id, const std::string &addon_version)
void async_get_and_send_game_history(boost::asio::io_context &io_service, wesnothd::server &s, any_socket_ptr socket, int player_id, int offset, std::string &search_game_name, int search_content_type, std::string &search_content)
Runs an asynchronous query to fetch the user's game history data.
void db_set_oos_flag(const std::string &uuid, int game_id)
Sets the OOS flag in the database if wesnothd is told by a client it has detected an OOS error.
std::string get_uuid()
bool user_is_addon_admin(const std::string &name)
std::string db_users_table_
The name of the phpbb users table.
bool user_is_moderator(const std::string &name)
void db_delete_addon_authors(const std::string &instance_version, const std::string &id)
Removes the authors information from addon_author for a particular addon and version.
void db_insert_addon_info(const std::string &instance_version, const std::string &id, const std::string &name, const std::string &type, const std::string &version, bool forum_auth, int topic_id, const std::string uploader)
Inserts information about an uploaded add-on into the database.
std::string get_hashed_password_from_db(const std::string &user)
void set_is_moderator(const std::string &name, const bool &is_moderator)
Sets or unsets whether the player should be considered a moderator in the extra table.
long get_forum_id(const std::string &name)
int mp_mod_group_
The group ID of the forums MP Moderators group.
void get_users_for_ip(const std::string &ip, std::ostringstream *out)
Searches for all players that logged in using the ip address.
std::chrono::system_clock::time_point get_registrationdate(const std::string &user)
bool db_topic_id_exists(int topic_id)
Checks whether a forum thread with topic_id exists.
ban_info user_is_banned(const std::string &name, const std::string &addr)
std::string db_extra_table_
The name of the extras custom table, not part of a phpbb database.
void db_update_game_end(const std::string &uuid, int game_id, const std::string &replay_location)
Update the game related information when the game ends.
std::string get_user_email(const std::string &user)
void async_test_query(boost::asio::io_context &io_service, int limit)
A simple test query for running a query asynchronously.
std::string extract_salt(const std::string &name)
Needed because the hashing algorithm used by phpbb requires some info from the original hash to recre...
std::chrono::system_clock::time_point get_lastlogin(const std::string &user)
void db_update_addon_download_count(const std::string &instance_version, const std::string &id, const std::string &version)
Increments the download count for this add-on for the specific version.
bool db_is_user_secondary_author(const std::string &instance_version, const std::string &id, const std::string &username)
Checks whether the provided username is a secondary author of the add-on.
std::string get_tournaments()
void db_insert_addon_authors(const std::string &instance_version, const std::string &id, const std::vector< std::string > &primary_authors, const std::vector< std::string > &secondary_authors)
Inserts rows for the primary and secondary authors for a particular addon and version.
void user_logged_in(const std::string &name)
Sets the last login time to the current time.
void get_ips_for_user(const std::string &username, std::ostringstream *out)
Searches for all ip addresses used by the player.
bool login(const std::string &name, const std::string &password)
Retrieves the player's hashed password from the phpbb forum database and checks if it matches the has...
fuh(const config &c)
Reads wesnothd's config for the data needed to initialize this class and dbconn.
int site_admin_group_
The group ID of the forums Site Administrators group.
bool user_is_active(const std::string &name)
std::string user_info(const std::string &name)
bool db_do_any_authors_exist(const std::string &instance_version, const std::string &id)
Checks whether any author information exists for a particular addon and version, since if there's no ...
bool db_is_user_primary_author(const std::string &instance_version, const std::string &id, const std::string &username)
Checks whether the provided username is the primary author of the add-on.
unsigned long long db_insert_login(const std::string &username, const std::string &ip, const std::string &version)
Inserts into the database for when a player logs in.
int forum_admin_group_
The group ID of the forums Forum Administrators group.
void db_insert_game_player_info(const std::string &uuid, int game_id, const std::string &username, int side_number, int is_host, const std::string &faction, const std::string &version, const std::string &source, const std::string &current_user, const std::string &leaders)
Inserts player information per side.
dbconn conn_
An instance of the class responsible for executing the queries and handling the database connection.
bool user_exists(const std::string &name)
config db_get_forum_auth_usage(const std::string &instance_version)
config db_get_addon_admins()
config db_get_addon_downloads_info(const std::string &instance_version, const std::string &id)
Gets a list of download count by version for add-ons.
unsigned long long db_insert_game_content_info(const std::string &uuid, int game_id, const std::string &type, const std::string &name, const std::string &id, const std::string &addon_id, const std::string &addon_version)
Inserts information about the content being played.
void db_update_logout(unsigned long long login_id)
Updates the database for when a player logs out.
void db_insert_game_info(const std::string &uuid, int game_id, const std::string &version, const std::string &name, int reload, int observers, int is_public, int has_password)
Inserts game related information.
@ BAN_EMAIL
Account email address ban.
@ BAN_IP
IP address ban.
@ BAN_USER
User account/name ban.
@ BAN_NONE
Not a ban.
static bool is_valid_prefix(const std::string &hash)
Definition hash.cpp:168
std::string get_salt() const
Definition hash.cpp:175
static bcrypt from_hash_string(const std::string &input)
Definition hash.cpp:149
static bool is_valid_hash(const std::string &hash)
Definition hash.cpp:99
Definitions for the interface to Wesnoth Markup Language (WML).
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition chrono.hpp:58
auto parse_timestamp(long long val)
Definition chrono.hpp:48
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition chrono.hpp:72
logger & err()
Definition log.cpp:339
logger & info()
Definition log.cpp:351
utils::variant< socket_ptr, tls_socket_ptr > any_socket_ptr
Ban status description.
static map_location::direction s
#define i
#define e
#define c
#define b