[Servatrice] Log room channel messages in audit log (#7479)

* [Servatrice] Log room channel messages and bump schema to 38

* Fixup: show room_channel audit rows and keep migration nullable

The log history query returns room_channel rows, but the log tab
dropped them. Render them in the room table with an extra Channel
column. Also drop the NOT NULL on target_type from the 37->38
migration so migrated schemas match the nullable servatrice.sql
definition (and strict-mode ALTERs cannot fail on legacy NULL rows).
Update the channel tests to the flag-based privilege model: the admin
fixture now carries IsModerator alongside IsAdmin, as the database
layer assigns.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-10-16 22:46:39 +02:00 • committed by GitHub
parent c99113ab0e
commit ce51e7147d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 603 additions and 14 deletions

View file

@ -18,4 +18,12 @@ CREATE TABLE IF NOT EXISTS `cockatrice_rooms_channels` (
FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
-- 2. Audit log: room_channel target type plus a channel column so moderators
-- can tell which stream a logged message belongs to.
ALTER TABLE `cockatrice_log`
MODIFY `target_type` ENUM('room', 'game', 'chat', 'room_channel');
ALTER TABLE `cockatrice_log`
ADD COLUMN `channel` varchar(50) NULL AFTER `target_name`;
UPDATE cockatrice_schema_version SET version=38 WHERE version=37;

View file

@ -341,9 +341,10 @@ CREATE TABLE IF NOT EXISTS `cockatrice_log` (
`sender_name` varchar(35) NOT NULL,
`sender_ip` varchar(45) NOT NULL,
`log_message` text NOT NULL,
`target_type` ENUM('room', 'game', 'chat'),
`target_type` ENUM('room', 'game', 'chat', 'room_channel'),
`target_id` int(7) NULL,
`target_name` varchar(50) NOT NULL,
`channel` varchar(50) NULL,
KEY `sender_name` (`sender_name`),
KEY `sender_ip` (`sender_ip`),
KEY `target_id` (`target_id`),

View file

@ -1286,7 +1286,8 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId,
const QString &logMessage,
LogMessage_TargetType targetType,
const int targetId,
const QString &targetName)
const QString &targetName,
const QString &channel)
{
QString targetTypeString;
switch (targetType) {
@ -1296,6 +1297,12 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId,
}
targetTypeString = "room";
break;
case MessageTargetRoomChannel:
if (!settingsCache->value("logging/log_user_msg_room", 0).toBool()) {
return;
}
targetTypeString = "room_channel";
break;
case MessageTargetGame:
if (!settingsCache->value("logging/log_user_msg_game", 0).toBool()) {
return;
@ -1319,8 +1326,9 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId,
}
QSqlQuery *query = prepareQuery("insert into {prefix}_log (log_time, sender_id, sender_name, sender_ip, "
"log_message, target_type, target_id, target_name) values (now(), :sender_id, "
":sender_name, :sender_ip, :log_message, :target_type, :target_id, :target_name)");
"log_message, target_type, target_id, target_name, channel) values (now(), "
":sender_id, :sender_name, :sender_ip, :log_message, :target_type, :target_id, "
":target_name, :channel)");
query->bindValue(":sender_id", senderId < 1 ? QVariant() : senderId);
query->bindValue(":sender_name", senderName);
query->bindValue(":sender_ip", senderIp);
@ -1328,6 +1336,7 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId,
query->bindValue(":target_type", targetTypeString);
query->bindValue(":target_id", (targetType == MessageTargetChat && targetId < 1) ? QVariant() : targetId);
query->bindValue(":target_name", targetName);
query->bindValue(":channel", channel.isEmpty() ? QVariant() : channel);
execSqlQuery(query);
}
@ -1846,9 +1855,9 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
if (room) {
if (game || chat) {
queryString.append(" OR `target_type` = 'room'");
queryString.append(" OR `target_type` = 'room' OR `target_type` = 'room_channel'");
} else {
queryString.append("`target_type` = 'room'");
queryString.append("`target_type` = 'room' OR `target_type` = 'room_channel'");
}
}
queryString.append(")");
@ -1899,6 +1908,7 @@ QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory
chatMessage.set_target_type(QString(query->value(5).toString()).toStdString());
chatMessage.set_target_id(QString(query->value(6).toString()).toStdString());
chatMessage.set_target_name(QString(query->value(7).toString()).toStdString());
chatMessage.set_channel(QString(query->value(8).toString()).toStdString());
results << chatMessage;
}

View file

@ -177,7 +177,8 @@ public:
const QString &logMessage,
LogMessage_TargetType targetType,
const int targetId,
const QString &targetName) override;
const QString &targetName,
const QString &channel = QString()) override;
bool changeUserPassword(const QString &user, const QString &password, bool passwordNeedsHash) override;
bool changeUserPassword(const QString &user,
const QString &oldPassword,