The Battle for Wesnoth  1.19.5+dev
forum_user_handler.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
20 #include "serialization/chrono.hpp"
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 
30 static 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 
36 namespace {
37  const int USER_INACTIVE = 1;
38  const int USER_IGNORE = 2;
39 }
40 
41 fuh::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 {
47  try {
48  mp_mod_group_ = std::stoi(c["mp_mod_group"].str());
49  } catch(...) {
50  ERR_UH << "Failed to convert the mp_mod_group value of '" << c["mp_mod_group"].str() << "' into an int! Defaulting to " << mp_mod_group_ << ".";
51  }
52 }
53 
54 bool fuh::login(const std::string& name, const std::string& password) {
55  // Retrieve users' password as hash
56  try {
57  std::string hash = get_hashed_password_from_db(name);
58 
59  if(utils::md5::is_valid_hash(hash) || utils::bcrypt::is_valid_prefix(hash)) { // md5 hash
60  return password == hash;
61  } else {
62  ERR_UH << "Invalid hash for user '" << name << "'";
63  return false;
64  }
65  } catch (const error& e) {
66  ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message;
67  return false;
68  }
69 }
70 
71 std::string fuh::extract_salt(const std::string& name) {
72 
73  // Some double security, this should never be needed
74  if(!(user_exists(name))) {
75  return "";
76  }
77 
78  std::string hash;
79 
80  try {
81  hash = get_hashed_password_from_db(name);
82  } catch (const error& e) {
83  ERR_UH << "Could not retrieve hash for user '" << name << "' :" << e.message;
84  return "";
85  }
86 
88  return hash.substr(0,12);
89 
91  try {
93  } catch(const utils::hash_error& err) {
94  ERR_UH << "Error getting salt from hash of user '" << name << "': " << err.what();
95  return "";
96  }
97  }
98 
99  return "";
100 }
101 
102 void fuh::user_logged_in(const std::string& name) {
103  auto now = chrono::serialize_timestamp(std::chrono::system_clock::now());
104  conn_.write_user_int("user_lastvisit", name, static_cast<int>(now));
105 }
106 
107 bool fuh::user_exists(const std::string& name) {
108  return conn_.user_exists(name);
109 }
110 
111 long fuh::get_forum_id(const std::string& name) {
112  return conn_.get_forum_id(name);
113 }
114 
115 bool fuh::user_is_active(const std::string& name) {
116  int user_type = conn_.get_user_int(db_users_table_, "user_type", name);
117  return user_type != USER_INACTIVE && user_type != USER_IGNORE;
118 }
119 
120 bool fuh::user_is_moderator(const std::string& name) {
121  if(!user_exists(name)){
122  return false;
123  }
124  return conn_.get_user_int(db_extra_table_, "user_is_moderator", name) == 1 || (mp_mod_group_ != 0 && conn_.is_user_in_group(name, mp_mod_group_));
125 }
126 
127 void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
128  if(!user_exists(name)){
129  return;
130  }
131  conn_.write_user_int("user_is_moderator", name, is_moderator);
132 }
133 
134 fuh::ban_info fuh::user_is_banned(const std::string& name, const std::string& addr)
135 {
136  ban_check b = conn_.get_ban_info(name, addr);
137  switch(b.get_ban_type())
138  {
139  case BAN_NONE:
140  return {};
141  case BAN_IP:
142  LOG_UH << "User '" << name << "' ip " << addr << " banned by IP address";
143  return { BAN_IP, b.get_ban_duration() };
144  case BAN_USER:
145  LOG_UH << "User '" << name << "' uid " << b.get_user_id() << " banned by uid";
146  return { BAN_USER, b.get_ban_duration() };
147  case BAN_EMAIL:
148  LOG_UH << "User '" << name << "' email " << b.get_email() << " banned by email address";
149  return { BAN_EMAIL, b.get_ban_duration() };
150  default:
151  ERR_UH << "Invalid ban type '" << b.get_ban_type() << "' returned for user '" << name << "'";
152  return {};
153  }
154 }
155 
156 std::string fuh::user_info(const std::string& name) {
157  if(!user_exists(name)) {
158  throw error("No user with the name '" + name + "' exists.");
159  }
160 
161  auto reg_date = get_registrationdate(name);
162  auto ll_date = get_lastlogin(name);
163 
164  static constexpr std::string_view format = "%a %b %d %T %Y"; // equivalent to std::ctime
165  std::string reg_string = chrono::format_local_timestamp(reg_date, format);
166  std::string ll_string;
167 
168  if(ll_date > decltype(ll_date){}) {
169  ll_string = chrono::format_local_timestamp(ll_date, format);
170  } else {
171  ll_string = "Never\n";
172  }
173 
174  std::stringstream info;
175  info << "Name: " << name << "\n"
176  << "Registered: " << reg_string
177  << "Last login: " << ll_string;
178  if(!user_is_active(name)) {
179  info << "This account is currently inactive.\n";
180  }
181 
182  return info.str();
183 }
184 
185 std::string fuh::get_hashed_password_from_db(const std::string& user) {
186  return conn_.get_user_string(db_users_table_, "user_password", user);
187 }
188 
189 std::string fuh::get_user_email(const std::string& user) {
190  return conn_.get_user_string(db_users_table_, "user_email", user);
191 }
192 
193 void fuh::db_update_addon_download_count(const std::string& instance_version, const std::string& id, const std::string& version) {
194  return conn_.update_addon_download_count(instance_version, id, version);
195 }
196 
197 std::chrono::system_clock::time_point fuh::get_lastlogin(const std::string& user) {
198  return chrono::parse_timestamp(conn_.get_user_int(db_extra_table_, "user_lastvisit", user));
199 }
200 
201 std::chrono::system_clock::time_point fuh::get_registrationdate(const std::string& user) {
202  return chrono::parse_timestamp(conn_.get_user_int(db_users_table_, "user_regdate", user));
203 }
204 
205 std::string fuh::get_uuid(){
206  return conn_.get_uuid();
207 }
208 
209 std::string fuh::get_tournaments(){
210  return conn_.get_tournaments();
211 }
212 
213 void fuh::async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset, std::string& search_game_name, int search_content_type, std::string& search_content) {
214  boost::asio::post([this, &s, player, player_id, offset, &io_service, search_game_name, search_content_type, search_content] {
215  boost::asio::post(io_service, [player, &s, doc = conn_.get_game_history(player_id, offset, search_game_name, search_content_type, search_content)]{
216  s.send_to_player(player, *doc);
217  });
218  });
219 }
220 
221 void 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){
222  conn_.insert_game_info(uuid, game_id, version, name, reload, observers, is_public, has_password);
223 }
224 
225 void fuh::db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location){
226  conn_.update_game_end(uuid, game_id, replay_location);
227 }
228 
229 void 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){
230  conn_.insert_game_player_info(uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders);
231 }
232 
233 unsigned 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){
234  return conn_.insert_game_content_info(uuid, game_id, type, name, id, addon_id, addon_version);
235 }
236 
237 void fuh::db_set_oos_flag(const std::string& uuid, int game_id){
238  conn_.set_oos_flag(uuid, game_id);
239 }
240 
241 void fuh::async_test_query(boost::asio::io_service& io_service, int limit) {
242  boost::asio::post([this, limit, &io_service] {
243  ERR_UH << "async test query starts!";
244  int i = conn_.async_test_query(limit);
245  boost::asio::post(io_service, [i]{ ERR_UH << "async test query output: " << i; });
246  });
247 }
248 
249 bool fuh::db_topic_id_exists(int topic_id) {
250  return conn_.topic_id_exists(topic_id);
251 }
252 
253 void 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) {
254  conn_.insert_addon_info(instance_version, id, name, type, version, forum_auth, topic_id, uploader);
255 }
256 
257 unsigned long long fuh::db_insert_login(const std::string& username, const std::string& ip, const std::string& version) {
258  return conn_.insert_login(username, ip, version);
259 }
260 
261 void fuh::db_update_logout(unsigned long long login_id) {
262  conn_.update_logout(login_id);
263 }
264 
265 void fuh::get_users_for_ip(const std::string& ip, std::ostringstream* out) {
266  conn_.get_users_for_ip(ip, out);
267 }
268 
269 void fuh::get_ips_for_user(const std::string& username, std::ostringstream* out) {
270  conn_.get_ips_for_user(username, out);
271 }
272 
273 bool fuh::db_is_user_primary_author(const std::string& instance_version, const std::string& id, const std::string& username) {
274  return conn_.is_user_author(instance_version, id, username, 1);
275 }
276 
277 bool fuh::db_is_user_secondary_author(const std::string& instance_version, const std::string& id, const std::string& username) {
278  return conn_.is_user_author(instance_version, id, username, 0);
279 }
280 
281 void fuh::db_delete_addon_authors(const std::string& instance_version, const std::string& id) {
282  conn_.delete_addon_authors(instance_version, id);
283 }
284 
285 void fuh::db_insert_addon_authors(const std::string& instance_version, const std::string& id, const std::string& primary_author, const std::vector<std::string>& secondary_authors) {
286  conn_.insert_addon_author(instance_version, id, primary_author, 1);
287 
288  // ignore any duplicate authors
289  std::set<std::string> inserted_authors;
290  inserted_authors.emplace(primary_author);
291 
292  for(const std::string& secondary_author : secondary_authors) {
293  if(inserted_authors.count(secondary_author) == 0) {
294  inserted_authors.emplace(secondary_author);
295  conn_.insert_addon_author(instance_version, id, secondary_author, 0);
296  }
297  }
298 }
299 
300 bool fuh::db_do_any_authors_exist(const std::string& instance_version, const std::string& id) {
301  return conn_.do_any_authors_exist(instance_version, id);
302 }
303 
304 #endif //HAVE_MYSQLPP
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
void delete_addon_authors(const std::string &instance_version, const std::string &id)
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)
bool topic_id_exists(int topic_id)
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)
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 ...
void get_ips_for_user(const std::string &username, std::ostringstream *out)
bool is_user_in_group(const std::string &name, int group_id)
bool user_exists(const std::string &name)
void get_users_for_ip(const std::string &ip, std::ostringstream *out)
void insert_addon_author(const std::string &instance_version, const std::string &id, const std::string author, int is_primary)
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.
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)
ban_check get_ban_info(const std::string &name, const std::string &ip)
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)
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 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()
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 db_insert_addon_authors(const std::string &instance_version, const std::string &id, const std::string &primary_author, const std::vector< std::string > &secondary_authors)
Inserts rows for the primary and secondary authors for a particular addon and version.
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)
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 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.
void async_test_query(boost::asio::io_service &io_service, int limit)
A simple test query for running a query asynchronously.
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.
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)
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 async_get_and_send_game_history(boost::asio::io_service &io_service, wesnothd::server &s, wesnothd::player_iterator player, 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_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:169
std::string get_salt() const
Definition: hash.cpp:176
static bcrypt from_hash_string(const std::string &input)
Definition: hash.cpp:150
static bool is_valid_hash(const std::string &hash)
Definition: hash.cpp:100
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1028
int side_number
Definition: game_info.hpp:40
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition: chrono.hpp:56
auto parse_timestamp(long long val)
Definition: chrono.hpp:46
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition: chrono.hpp:61
logger & err()
Definition: log.cpp:307
logger & info()
Definition: log.cpp:319
player_connections::const_iterator player_iterator
Ban status description.
mock_char c
static map_location::direction s
#define e
#define b