mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
[Server/Client/Protocol] Reporting users + moderation queue functionality (#7091)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker Image / amd64 & arm64 (push) Waiting to run
* [Server/Client/Protocol] Reporting users + moderation queue functionality Took 6 minutes Took 3 minutes Took 8 seconds Took 11 minutes Took 12 minutes Took 7 minutes Took 15 seconds Took 2 minutes Took 1 minute Took 30 seconds Took 16 seconds * CI Fix Took 6 minutes * CI Fix Took 6 minutes * [Protocol] Add moderation investigation commands Adds the protocol layer for the moderation investigation suite: - Command_GetUserSessions/GetUserAlts/GetModeratorLastLogins/ResetUserPassword/RemoveUserAvatar (1013-1017) - Response extensions 1215-1219 with ServerInfo messages for sessions, alts, and staff logins - last_login on Response_ReportUserInfo and warning_il on Response_WarnList Took 2 minutes * [Utility] Add warning categories parser with infraction levels Parses the server's 'officialwarnings' setting (comma-separated, optional '|IL' suffix) into WarningCategory structs so the client can display the infraction level of each warning category. Includes GTest coverage. * [Server] Add moderation investigation tools Implements the server side of the moderation suite: - getUserSessions/getUserAlts/getModeratorLastLogins/removeUserAvatar DB methods - Handlers for all five new commands with audit records (PASSWORD_RESET, REMOVE_USER_AVATAR); password resets return a generated temporary password - cmdGetWarnList now reports per-category infraction levels from the officialwarnings setting; cmdReportUserInfo reports last_login - Update servatrice.ini.example with the warning taxonomy - Password/avatar mutations report RespNameNotFound when the user does not exist * [Client] Add moderation tab with investigate, password reset, and avatar removal - New Moderation tab: search a user to show account info, alternate accounts, login sessions, and staff last logins; actions to reset the user's password (shows the generated temporary password) and remove the user's avatar - 'Investigate user' entry in the user context menu opens the tab pre-loaded for that user - Warning dialog shows the infraction level of each warning category - Tab wired into TabSupervisor with a moderator-gated menu action, shortcut, and tabs.ini persistence (default closed) * [Server/Client/Protocol] Address PR #7091 review: security, bug, and perf fixes Security: - Promote RESET_USER_PASSWORD to admin-only dispatch (was moderator-accessible) - Reject password reset on users with equal/higher privilege than caller - Notify affected user via Event_NotifyUser::CUSTOM when password is reset - Add server-side category whitelist for reports - Drop reporter name fallback in comment/details authorization (ID-only) - Force password change: new DB column + login enforcement + client disconnect Bugs: - XSS via QTextEdit::append() → insertPlainText() in report tab and utils - allNotified initialized to true even with empty recipients list - Warning combo box: use currentData() instead of baked-in display text - Report resolution now records who resolved (resolved_by column + audit) Performance: - IP-correlation subquery: add 6-month window + LIMIT 200 - getUserSessions: clamp limit to 500 Non-blocking: - Palette-aware colors in report_utils.cpp (dark/light mode) - Report list pagination: offset/limit fields + total_count in response - SessionCommand enum gap comment for reserved values 1201-1203 Schema: 36→37 (force_password_change), 37→38 (resolved_by) Took 12 minutes Took 16 seconds * Fix macOs pedantry Took 5 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
9eafd90a91
commit
ed4eb1cb31
83 changed files with 4768 additions and 43 deletions
53
servatrice/migrations/servatrice_0035_to_0036.sql
Normal file
53
servatrice/migrations/servatrice_0035_to_0036.sql
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
-- Servatrice db migration from version 35 to version 36
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`reporter_id` int(7) unsigned NULL,
|
||||
`reporter_name` varchar(35) NOT NULL,
|
||||
`reported_user_id` int(7) unsigned NULL,
|
||||
`reported_user_name` varchar(35) NOT NULL,
|
||||
`game_id` int(7) unsigned NULL,
|
||||
`room_id` int(7) unsigned NULL,
|
||||
`category` varchar(255) NOT NULL,
|
||||
`description` text NOT NULL,
|
||||
`chat_log` mediumtext NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`resolution_time` datetime NULL,
|
||||
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
||||
`assigned_to` int(7) unsigned NULL,
|
||||
`resolved_by` int(7) unsigned NULL,
|
||||
`resolution_note` text,
|
||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_created` (`created_at`),
|
||||
INDEX `idx_reporter_id_created` (`reporter_id`, `created_at`),
|
||||
INDEX `idx_reported_user_name` (`reported_user_name`),
|
||||
INDEX `idx_status_created` (`status`, `created_at`),
|
||||
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`resolved_by`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`report_id` int(11) unsigned NOT NULL,
|
||||
`author_name` varchar(35) NOT NULL,
|
||||
`author_id` int(7) unsigned NULL,
|
||||
`comment_text` text NOT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`is_moderator` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_report_id` (`report_id`),
|
||||
INDEX `idx_notified` (`notified`),
|
||||
FOREIGN KEY (`report_id`) REFERENCES `cockatrice_reports`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`author_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `cockatrice_users`
|
||||
ADD COLUMN `force_password_change` tinyint(1) NOT NULL DEFAULT 0
|
||||
AFTER `passwordLastChangedDate`;
|
||||
|
||||
UPDATE cockatrice_schema_version SET version=36 WHERE version=35;
|
||||
|
|
@ -79,8 +79,10 @@ requiredfeatures=""
|
|||
|
||||
; You can define custom warnings that users are sent when the moderation staff uses the right client warn user
|
||||
; menu option. This list is comma seperated that each item will appear in the drop down list for staff members
|
||||
; to choose from. Example: "Flaming,Foul Language"
|
||||
officialwarnings="Flaming,Spamming,Causing Drama,Abusive Language"
|
||||
; to choose from. Each entry may optionally carry a recommended starting intervention level (see the moderator
|
||||
; guide) by appending "|" and the level number. Entries without an explicit level default to intervention
|
||||
; level 1. Example: "Flaming,Foul Language"
|
||||
officialwarnings="Abusive Language|1,Calling Out User|1,Causing Drama|1,Cheating|2,Disrespecting Staff|1,Disrupting a Draft|1,Inappropriate Avatar|3,Inappropriate Game Name|1,Kicking Without Valid Reason|1,Spamming|1,Targeted Harassment|2"
|
||||
|
||||
; Maximum time in seconds a player can stay connected but idle. Default is 3600 (0 = disabled)
|
||||
; Clients will be notified at the 90% time period of pending disconnection if they do not take action.
|
||||
|
|
@ -373,6 +375,13 @@ command_counting_interval=10
|
|||
; Maximum number of game commands in an interval before new commands gets dropped; default is 20
|
||||
max_command_count_per_interval=20
|
||||
|
||||
[reporting]
|
||||
; Maximum number of user reports a single user can file per day; default is 10; set to 0 to disable the limit
|
||||
max_reports_per_day=10
|
||||
|
||||
; Maximum number of report comments a single user can post per hour; default is 30; set to 0 to disable the limit
|
||||
max_comments_per_hour=30
|
||||
|
||||
[logging]
|
||||
; Admin/Moderators can query the stored logs for information when looking up reports by various players. This
|
||||
; option can allow or disallow them from doing so.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` (
|
|||
PRIMARY KEY (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO cockatrice_schema_version VALUES(35);
|
||||
INSERT INTO cockatrice_schema_version VALUES(36);
|
||||
|
||||
-- users and user data tables
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||
|
|
@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
|||
`privlevelStartDate` datetime NOT NULL,
|
||||
`privlevelEndDate` datetime NOT NULL,
|
||||
`passwordLastChangedDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`force_password_change` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`leftPawnColorOverride` varchar(255),
|
||||
`rightPawnColorOverride` varchar(255),
|
||||
`card_art_params` TEXT DEFAULT NULL,
|
||||
|
|
@ -233,6 +234,52 @@ CREATE TABLE IF NOT EXISTS `cockatrice_warnings` (
|
|||
INDEX `idx_user_name` (`user_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_reports` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`reporter_id` int(7) unsigned NULL,
|
||||
`reporter_name` varchar(35) NOT NULL,
|
||||
`reported_user_id` int(7) unsigned NULL,
|
||||
`reported_user_name` varchar(35) NOT NULL,
|
||||
`game_id` int(7) unsigned NULL,
|
||||
`room_id` int(7) unsigned NULL,
|
||||
`category` varchar(255) NOT NULL,
|
||||
`description` text NOT NULL,
|
||||
`chat_log` mediumtext NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`resolution_time` datetime NULL,
|
||||
`status` enum('open','assigned','resolved','dismissed') NOT NULL DEFAULT 'open',
|
||||
`assigned_to` int(7) unsigned NULL,
|
||||
`resolved_by` int(7) unsigned NULL,
|
||||
`resolution_note` text,
|
||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_created` (`created_at`),
|
||||
INDEX `idx_reporter_id_created` (`reporter_id`, `created_at`),
|
||||
INDEX `idx_reported_user_name` (`reported_user_name`),
|
||||
INDEX `idx_status_created` (`status`, `created_at`),
|
||||
FOREIGN KEY (`reporter_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`reported_user_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`assigned_to`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`resolved_by`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_report_comments` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`report_id` int(11) unsigned NOT NULL,
|
||||
`author_name` varchar(35) NOT NULL,
|
||||
`author_id` int(7) unsigned NULL,
|
||||
`comment_text` text NOT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`is_moderator` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`notified` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `idx_report_id` (`report_id`),
|
||||
INDEX `idx_notified` (`notified`),
|
||||
FOREIGN KEY (`report_id`) REFERENCES `cockatrice_reports`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY (`author_id`) REFERENCES `cockatrice_users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_log` (
|
||||
`log_time` datetime NOT NULL,
|
||||
`sender_id` int(7) unsigned NULL,
|
||||
|
|
|
|||
|
|
@ -514,6 +514,23 @@ QList<ServerProperties> Servatrice::getServerList() const
|
|||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<const Response_ReportStats> Servatrice::getCachedReportStats() const
|
||||
{
|
||||
QMutexLocker locker(&reportStatsMutex);
|
||||
if (!reportStatsTimestamp.isValid() ||
|
||||
reportStatsTimestamp.secsTo(QDateTime::currentDateTime()) >= reportStatsCacheTtlSeconds) {
|
||||
return nullptr;
|
||||
}
|
||||
return reportStatsCache;
|
||||
}
|
||||
|
||||
void Servatrice::cacheReportStats(const Response_ReportStats &stats)
|
||||
{
|
||||
QMutexLocker locker(&reportStatsMutex);
|
||||
reportStatsCache = std::make_shared<const Response_ReportStats>(stats);
|
||||
reportStatsTimestamp = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
int Servatrice::getUsersWithAddress(const QHostAddress &address) const
|
||||
{
|
||||
int result = 0;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#ifndef SERVATRICE_H
|
||||
#define SERVATRICE_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
#include <QMetaType>
|
||||
#include <QMutex>
|
||||
|
|
@ -29,6 +30,8 @@
|
|||
#include <QSslKey>
|
||||
#include <QTcpServer>
|
||||
#include <QWebSocketServer>
|
||||
#include <libcockatrice/protocol/pb/response_report_stats.pb.h>
|
||||
#include <memory>
|
||||
#include <server.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -173,6 +176,11 @@ private:
|
|||
int nextShutdownMessageMinutes;
|
||||
QTimer *shutdownTimer;
|
||||
|
||||
mutable QMutex reportStatsMutex;
|
||||
QDateTime reportStatsTimestamp;
|
||||
std::shared_ptr<const Response_ReportStats> reportStatsCache;
|
||||
static constexpr int reportStatsCacheTtlSeconds = 60;
|
||||
|
||||
mutable QMutex serverListMutex;
|
||||
QList<ServerProperties> serverList;
|
||||
void updateServerList();
|
||||
|
|
@ -283,6 +291,11 @@ public:
|
|||
void removeIslInterface(int _serverId);
|
||||
QReadWriteLock islLock;
|
||||
|
||||
// The moderation queue statistics are shared between all connected moderators and
|
||||
// cached briefly to avoid re-running several full-table queries on every refresh.
|
||||
std::shared_ptr<const Response_ReportStats> getCachedReportStats() const;
|
||||
void cacheReportStats(const Response_ReportStats &stats);
|
||||
|
||||
QList<ServerProperties> getServerList() const;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <QSqlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/protocol/pb/game_replay.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/utility/passwordhasher.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DatabaseInterfaceLog, "database_interface");
|
||||
|
|
@ -339,8 +340,8 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
return UserIsBanned;
|
||||
}
|
||||
|
||||
QSqlQuery *passwordQuery =
|
||||
prepareQuery("select password_sha512, active from {prefix}_users where name = :name");
|
||||
QSqlQuery *passwordQuery = prepareQuery(
|
||||
"select password_sha512, active, force_password_change from {prefix}_users where name = :name");
|
||||
passwordQuery->bindValue(":name", user);
|
||||
if (!execSqlQuery(passwordQuery)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Login denied: SQL error";
|
||||
|
|
@ -350,6 +351,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
if (passwordQuery->next()) {
|
||||
const QString correctPasswordSha512 = passwordQuery->value(0).toString();
|
||||
const bool userIsActive = passwordQuery->value(1).toBool();
|
||||
const bool forceChange = passwordQuery->value(2).toBool();
|
||||
if (!userIsActive) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Login denied: user not active";
|
||||
return UserIsInactive;
|
||||
|
|
@ -361,6 +363,10 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot
|
|||
hashedPassword = password;
|
||||
}
|
||||
if (correctPasswordSha512 == hashedPassword) {
|
||||
if (forceChange) {
|
||||
qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required";
|
||||
return PasswordChangeRequired;
|
||||
}
|
||||
qCDebug(DatabaseInterfaceLog) << "Login accepted: password right";
|
||||
return PasswordRight;
|
||||
} else {
|
||||
|
|
@ -1084,11 +1090,22 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
|
|||
"passwordLastChangedDate = NOW() where name = :name");
|
||||
passwordQuery->bindValue(":password", passwordSha512);
|
||||
passwordQuery->bindValue(":name", user);
|
||||
if (execSqlQuery(passwordQuery)) {
|
||||
return true;
|
||||
if (!execSqlQuery(passwordQuery)) {
|
||||
return false;
|
||||
}
|
||||
return passwordQuery->numRowsAffected() > 0;
|
||||
}
|
||||
|
||||
void Servatrice_DatabaseInterface::setForcePasswordChange(const QString &user, bool force)
|
||||
{
|
||||
if (!checkSql()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET force_password_change = :force WHERE name = :name");
|
||||
query->bindValue(":force", force ? 1 : 0);
|
||||
query->bindValue(":name", user);
|
||||
execSqlQuery(query);
|
||||
}
|
||||
|
||||
bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user,
|
||||
|
|
@ -1314,6 +1331,169 @@ QList<ServerInfo_Warning> Servatrice_DatabaseInterface::getUserWarnHistory(const
|
|||
return results;
|
||||
}
|
||||
|
||||
QList<ServerInfo_UserSession> Servatrice_DatabaseInterface::getUserSessions(const QString &userName, int limit)
|
||||
{
|
||||
QList<ServerInfo_UserSession> results;
|
||||
|
||||
if (!checkSql()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
QSqlQuery *query = prepareQuery("SELECT user_name, ip_address, clientid, "
|
||||
"UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), connection_type "
|
||||
"FROM {prefix}_sessions WHERE user_name = :user_name "
|
||||
"ORDER BY start_time DESC LIMIT :limit");
|
||||
query->bindValue(":user_name", userName);
|
||||
query->bindValue(":limit", limit);
|
||||
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to collect session history information: SQL Error";
|
||||
return results;
|
||||
}
|
||||
|
||||
while (query->next()) {
|
||||
ServerInfo_UserSession sessionDetails;
|
||||
sessionDetails.set_user_name(query->value(0).toString().toStdString());
|
||||
sessionDetails.set_ip_address(query->value(1).toString().toStdString());
|
||||
sessionDetails.set_clientid(query->value(2).toString().toStdString());
|
||||
sessionDetails.set_start_time(query->value(3).toLongLong());
|
||||
if (!query->value(4).isNull()) {
|
||||
sessionDetails.set_end_time(query->value(4).toLongLong());
|
||||
}
|
||||
sessionDetails.set_connection_type(query->value(5).toString().toStdString());
|
||||
results << sessionDetails;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QString &userName)
|
||||
{
|
||||
QList<ServerInfo_UserAlt> results;
|
||||
|
||||
if (!checkSql()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
// Seed account identifiers used to find related accounts
|
||||
QSqlQuery *seedQuery = prepareQuery("SELECT email, clientid FROM {prefix}_users WHERE name = :user_name");
|
||||
seedQuery->bindValue(":user_name", userName);
|
||||
if (!execSqlQuery(seedQuery) || !seedQuery->next()) {
|
||||
return results;
|
||||
}
|
||||
const QString seedEmail = seedQuery->value(0).toString();
|
||||
const QString seedClientId = seedQuery->value(1).toString();
|
||||
|
||||
QString queryString = "SELECT u.name, u.email, u.clientid, UNIX_TIMESTAMP(u.registrationDate), "
|
||||
"UNIX_TIMESTAMP(a.last_login), "
|
||||
"(SELECT COUNT(*) FROM {prefix}_warnings w WHERE w.user_id = u.id), "
|
||||
"(SELECT COUNT(*) FROM {prefix}_bans b WHERE b.user_name = u.name), "
|
||||
"u.active "
|
||||
"FROM {prefix}_users u "
|
||||
"LEFT JOIN {prefix}_user_analytics a ON a.id = u.id "
|
||||
"WHERE u.name = :user_name";
|
||||
if (!seedEmail.isEmpty()) {
|
||||
queryString.append(" OR u.email = :seed_email");
|
||||
}
|
||||
if (!seedClientId.isEmpty()) {
|
||||
queryString.append(" OR u.clientid = :seed_clientid");
|
||||
}
|
||||
queryString.append(" OR u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s "
|
||||
"WHERE s.ip_address IN (SELECT DISTINCT s2.ip_address FROM {prefix}_sessions s2 "
|
||||
"WHERE s2.user_name = :user_name"
|
||||
" AND s2.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH))"
|
||||
" AND s.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH)) "
|
||||
"ORDER BY u.name LIMIT 200");
|
||||
|
||||
QSqlQuery *query = prepareQuery(queryString);
|
||||
query->bindValue(":user_name", userName);
|
||||
if (!seedEmail.isEmpty()) {
|
||||
query->bindValue(":seed_email", seedEmail);
|
||||
}
|
||||
if (!seedClientId.isEmpty()) {
|
||||
query->bindValue(":seed_clientid", seedClientId);
|
||||
}
|
||||
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to collect user alt information: SQL Error";
|
||||
return results;
|
||||
}
|
||||
|
||||
while (query->next()) {
|
||||
ServerInfo_UserAlt altDetails;
|
||||
altDetails.set_user_name(query->value(0).toString().toStdString());
|
||||
altDetails.set_email(query->value(1).toString().toStdString());
|
||||
altDetails.set_clientid(query->value(2).toString().toStdString());
|
||||
altDetails.set_registration_time(query->value(3).toLongLong());
|
||||
if (!query->value(4).isNull()) {
|
||||
altDetails.set_last_login(query->value(4).toLongLong());
|
||||
}
|
||||
altDetails.set_warn_count(query->value(5).toInt());
|
||||
altDetails.set_ban_count(query->value(6).toInt());
|
||||
altDetails.set_is_active(query->value(7).toBool());
|
||||
results << altDetails;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastLogins()
|
||||
{
|
||||
QList<ServerInfo_ModeratorLogin> results;
|
||||
|
||||
if (!checkSql()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
QSqlQuery *query = prepareQuery("SELECT u.name, u.admin, UNIX_TIMESTAMP(a.last_login) "
|
||||
"FROM {prefix}_users u "
|
||||
"LEFT JOIN {prefix}_user_analytics a ON a.id = u.id "
|
||||
"WHERE (u.admin & 7) <> 0 ORDER BY u.name");
|
||||
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to collect moderator login information: SQL Error";
|
||||
return results;
|
||||
}
|
||||
|
||||
while (query->next()) {
|
||||
ServerInfo_ModeratorLogin loginDetails;
|
||||
loginDetails.set_user_name(query->value(0).toString().toStdString());
|
||||
|
||||
const int isAdmin = query->value(1).toInt();
|
||||
int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered;
|
||||
if (isAdmin & 1) {
|
||||
userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator;
|
||||
} else if (isAdmin & 2) {
|
||||
userLevel |= ServerInfo_User::IsModerator;
|
||||
}
|
||||
if (isAdmin & 4) {
|
||||
userLevel |= ServerInfo_User::IsJudge;
|
||||
}
|
||||
loginDetails.set_user_level(userLevel);
|
||||
|
||||
if (!query->value(2).isNull()) {
|
||||
loginDetails.set_last_login(query->value(2).toLongLong());
|
||||
}
|
||||
results << loginDetails;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
|
||||
{
|
||||
if (!checkSql()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET avatar_bmp = '' WHERE name = :user_name");
|
||||
query->bindValue(":user_name", userName);
|
||||
if (!execSqlQuery(query)) {
|
||||
return false;
|
||||
}
|
||||
return query->numRowsAffected() > 0;
|
||||
}
|
||||
|
||||
QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user,
|
||||
const QString &ipaddress,
|
||||
const QString &gamename,
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@
|
|||
#include <QObject>
|
||||
#include <QSqlDatabase>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_moderator_login.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user_alt.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user_session.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_warning.pb.h>
|
||||
#include <server.h>
|
||||
#include <server_database_interface.h>
|
||||
|
||||
#define DATABASE_SCHEMA_VERSION 35
|
||||
#define DATABASE_SCHEMA_VERSION 36
|
||||
|
||||
class Servatrice;
|
||||
|
||||
|
|
@ -119,6 +122,7 @@ public:
|
|||
bool oldPasswordNeedsHash,
|
||||
const QString &newPassword,
|
||||
bool newPasswordNeedsHash) override;
|
||||
void setForcePasswordChange(const QString &user, bool force) override;
|
||||
QList<ServerInfo_Ban> getUserBanHistory(const QString userName);
|
||||
bool
|
||||
addWarning(const QString userName, const QString adminName, const QString warningReason, const QString clientID);
|
||||
|
|
@ -133,6 +137,10 @@ public:
|
|||
bool &room,
|
||||
int &range,
|
||||
int &maxresults);
|
||||
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
|
||||
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
|
||||
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
||||
bool removeUserAvatar(const QString &userName);
|
||||
bool addForgotPassword(const QString &user);
|
||||
bool removeForgotPassword(const QString &user) override;
|
||||
bool doesForgotPasswordExist(const QString &user);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,16 @@
|
|||
#include <QMutex>
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocket>
|
||||
#include <libcockatrice/protocol/pb/command_replay_download_by_game_id.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_add_comment.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_assign.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_my_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
|
||||
#include <server_protocolhandler.h>
|
||||
|
||||
class Servatrice;
|
||||
|
|
@ -50,6 +60,7 @@ class Command_BanFromServer;
|
|||
class Command_UpdateServerMessage;
|
||||
class Command_ShutdownServer;
|
||||
class Command_ReloadConfig;
|
||||
class Command_ReplayDownloadByGameId;
|
||||
|
||||
class Command_AccountEdit;
|
||||
class Command_AccountImage;
|
||||
|
|
@ -67,7 +78,7 @@ signals:
|
|||
void incTxBytes(qint64 amount);
|
||||
|
||||
protected:
|
||||
void logDebugMessage(const QString &message);
|
||||
void logDebugMessage(const QString &message) override;
|
||||
bool tooManyRegistrationAttempts(const QString &ipAddress);
|
||||
|
||||
virtual void writeToSocket(QByteArray &data) = 0;
|
||||
|
|
@ -102,6 +113,7 @@ private:
|
|||
Response::ResponseCode cmdReplayGetCode(const Command_ReplayGetCode &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
|
||||
|
|
@ -109,6 +121,10 @@ private:
|
|||
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReportAssign(const Command_ReportAssign &cmd, ResponseContainer &);
|
||||
Response::ResponseCode cmdReportResolve(const Command_ReportResolve &cmd, ResponseContainer &);
|
||||
Response::ResponseCode cmdReportUserInfo(const Command_ReportUserInfo &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReportStats(const Command_ReportStats &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */);
|
||||
Response::ResponseCode cmdReloadConfig(const Command_ReloadConfig & /* cmd */, ResponseContainer & /*rc*/);
|
||||
|
|
@ -122,10 +138,19 @@ private:
|
|||
Response::ResponseCode cmdForgotPasswordChallenge(const Command_ForgotPasswordChallenge &cmd,
|
||||
ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReport(const Command_Report &cmd, ResponseContainer &);
|
||||
Response::ResponseCode cmdReportMyList(const Command_ReportMyList &cmd, ResponseContainer &rc);
|
||||
void sendPendingReportNotifications(ResponseContainer &rc);
|
||||
void onLogin(ResponseContainer &rc) override;
|
||||
Response::ResponseCode cmdReportDetails(const Command_ReportDetails &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReportAddComment(const Command_ReportAddComment &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdReplayDownloadByGameId(const Command_ReplayDownloadByGameId &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode
|
||||
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc);
|
||||
processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc) override;
|
||||
Response::ResponseCode
|
||||
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc) override;
|
||||
Response::ResponseCode
|
||||
processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc) override;
|
||||
|
||||
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
|
||||
|
|
@ -141,6 +166,12 @@ private:
|
|||
Response::ResponseCode cmdGetAdminNotes(const Command_GetAdminNotes &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdUpdateAdminNotes(const Command_UpdateAdminNotes &cmd, ResponseContainer &rc);
|
||||
|
||||
Response::ResponseCode cmdGetUserSessions(const Command_GetUserSessions &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetUserAlts(const Command_GetUserAlts &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
|
||||
|
||||
bool addAdminFlagToUser(const QString &user, int flag);
|
||||
bool removeAdminFlagFromUser(const QString &user, int flag);
|
||||
|
||||
|
|
@ -157,9 +188,9 @@ public:
|
|||
bool initSession();
|
||||
|
||||
virtual QHostAddress getPeerAddress() const = 0;
|
||||
virtual QString getAddress() const = 0;
|
||||
QString getAddress() const override = 0;
|
||||
|
||||
void transmitProtocolItem(const ServerMessage &item);
|
||||
void transmitProtocolItem(const ServerMessage &item) override;
|
||||
};
|
||||
|
||||
class TcpServerSocketInterface : public AbstractServerSocketInterface
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue