mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[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:
parent
c99113ab0e
commit
ce51e7147d
9 changed files with 603 additions and 14 deletions
|
|
@ -35,10 +35,10 @@ TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _can
|
|||
: Tab(_tabSupervisor), client(_client), canUseDeveloperCommands(_canUseDeveloperCommands)
|
||||
{
|
||||
roomTable = new QTableWidget();
|
||||
roomTable->setColumnCount(6);
|
||||
roomTable->setColumnCount(7);
|
||||
roomTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
roomTable->setHorizontalHeaderLabels(
|
||||
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
|
||||
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName;Channel")).split(";"));
|
||||
|
||||
gameTable = new QTableWidget();
|
||||
gameTable->setColumnCount(6);
|
||||
|
|
@ -317,7 +317,8 @@ void TabLog::viewLogHistory_processResponse(const Response &resp)
|
|||
|
||||
for (int i = 0; i < response.log_message_size(); ++i) {
|
||||
ServerInfo_ChatMessage message = response.log_message(i);
|
||||
if (QString::fromStdString(message.target_type()) == "room") {
|
||||
const QString targetType = QString::fromStdString(message.target_type());
|
||||
if (targetType == "room" || targetType == "room_channel") {
|
||||
roomTable->insertRow(roomCounter);
|
||||
roomTable->setItem(roomCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
roomTable->setItem(roomCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
|
|
@ -325,10 +326,11 @@ void TabLog::viewLogHistory_processResponse(const Response &resp)
|
|||
roomTable->setItem(roomCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
|
||||
roomTable->setItem(roomCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
|
||||
roomTable->setItem(roomCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
|
||||
roomTable->setItem(roomCounter, 6, new QTableWidgetItem(QString::fromStdString(message.channel())));
|
||||
++roomCounter;
|
||||
}
|
||||
|
||||
if (QString::fromStdString(message.target_type()) == "game") {
|
||||
if (targetType == "game") {
|
||||
gameTable->insertRow(gameCounter);
|
||||
gameTable->setItem(gameCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
gameTable->setItem(gameCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
|
|
@ -339,7 +341,7 @@ void TabLog::viewLogHistory_processResponse(const Response &resp)
|
|||
++gameCounter;
|
||||
}
|
||||
|
||||
if (QString::fromStdString(message.target_type()) == "chat") {
|
||||
if (targetType == "chat") {
|
||||
chatTable->insertRow(chatCounter);
|
||||
chatTable->setItem(chatCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
chatTable->setItem(chatCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ public:
|
|||
MessageTargetRoom,
|
||||
MessageTargetGame,
|
||||
MessageTargetChat,
|
||||
MessageTargetIslRoom
|
||||
MessageTargetIslRoom,
|
||||
MessageTargetRoomChannel
|
||||
};
|
||||
virtual void logMessage(const int /* senderId */,
|
||||
const QString & /* senderName */,
|
||||
|
|
@ -159,7 +160,8 @@ public:
|
|||
const QString & /* logMessage */,
|
||||
LogMessage_TargetType /* targetType */,
|
||||
const int /* targetId */,
|
||||
const QString & /* targetName */)
|
||||
const QString & /* targetName */,
|
||||
const QString & /* channel */ = QString())
|
||||
{
|
||||
}
|
||||
virtual bool checkUserIsBanned(Server_ProtocolHandler * /* session */,
|
||||
|
|
|
|||
|
|
@ -1003,6 +1003,10 @@ Response::ResponseCode Server_ProtocolHandler::cmdRoomChannelSay(const Command_R
|
|||
|
||||
room->say(QString::fromStdString(userInfo->name()), msg, channelId);
|
||||
|
||||
databaseInterface->logMessage(
|
||||
userInfo->id(), QString::fromStdString(userInfo->name()), QString::fromStdString(userInfo->address()), msg,
|
||||
Server_DatabaseInterface::MessageTargetRoomChannel, room->getId(), room->getName(), channelId);
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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`),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -274,6 +274,17 @@ cockatrice_add_test(
|
|||
LABELS
|
||||
server
|
||||
)
|
||||
cockatrice_add_test(
|
||||
NAME
|
||||
server_room_channels_test
|
||||
SOURCES
|
||||
server_room_channels_test.cpp
|
||||
LIBS
|
||||
libcockatrice_network_server_remote
|
||||
libcockatrice_rng
|
||||
LABELS
|
||||
server
|
||||
)
|
||||
cockatrice_add_test(
|
||||
NAME
|
||||
latency_tracker_test
|
||||
|
|
|
|||
550
tests/server_room_channels_test.cpp
Normal file
550
tests/server_room_channels_test.cpp
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
/** @file server_room_channels_test.cpp
|
||||
* @brief Tests for room chat channels: fan-out, privilege gating, whitelist, history replay and audit logging.
|
||||
* @ingroup Tests
|
||||
*/
|
||||
|
||||
#include "libcockatrice/protocol/pb/response.pb.h"
|
||||
#include "libcockatrice/protocol/pb/server_message.pb.h"
|
||||
#include "server.h"
|
||||
#include "server_database_interface.h"
|
||||
#include "server_protocolhandler.h"
|
||||
#include "server_room.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/protocol/get_pb_extension.h>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_room_channel_say.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
|
||||
#include <libcockatrice/protocol/pb/isl_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/room_event.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_room_channel.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
#include <string>
|
||||
|
||||
RNG_Abstract *rng = nullptr; // referenced by the server_remote library
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class MockDatabaseInterface : public Server_DatabaseInterface
|
||||
{
|
||||
public:
|
||||
struct LogEntry
|
||||
{
|
||||
QString senderName;
|
||||
QString message;
|
||||
LogMessage_TargetType targetType;
|
||||
int targetId;
|
||||
QString targetName;
|
||||
QString channel;
|
||||
};
|
||||
QList<LogEntry> logEntries;
|
||||
|
||||
void logMessage(const int /* senderId */,
|
||||
const QString &senderName,
|
||||
const QString & /* senderIp */,
|
||||
const QString &message,
|
||||
LogMessage_TargetType targetType,
|
||||
const int targetId,
|
||||
const QString &targetName,
|
||||
const QString &channel = QString()) override
|
||||
{
|
||||
logEntries.append({senderName, message, targetType, targetId, targetName, channel});
|
||||
}
|
||||
|
||||
AuthenticationResult checkUserPassword(Server_ProtocolHandler *,
|
||||
const QString &,
|
||||
const QString &,
|
||||
const QString &,
|
||||
QString &,
|
||||
int &,
|
||||
bool) override
|
||||
{
|
||||
return NotLoggedIn;
|
||||
}
|
||||
int getNextReplayId() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int getNextGameId() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int getActiveUserCount(QString) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
ServerInfo_User getUserData(const QString &, bool) override
|
||||
{
|
||||
return ServerInfo_User();
|
||||
}
|
||||
};
|
||||
|
||||
class FakeServer : public Server
|
||||
{
|
||||
public:
|
||||
explicit FakeServer(Server_DatabaseInterface *db) : Server()
|
||||
{
|
||||
setDatabaseInterface(db);
|
||||
}
|
||||
void insertRoom(Server_Room *newRoom)
|
||||
{
|
||||
addRoom(newRoom);
|
||||
}
|
||||
};
|
||||
|
||||
class TestRoomHandler : public Server_ProtocolHandler
|
||||
{
|
||||
public:
|
||||
TestRoomHandler(Server *_server, Server_DatabaseInterface *_db) : Server_ProtocolHandler(_server, _db)
|
||||
{
|
||||
}
|
||||
|
||||
QString getAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
QString getConnectionType() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void authenticate(const ServerInfo_User &userInfo_)
|
||||
{
|
||||
setUserInfo(userInfo_);
|
||||
authState = PasswordRight;
|
||||
}
|
||||
void attachToRoom(Server_Room *targetRoom)
|
||||
{
|
||||
rooms.insert(targetRoom->getId(), targetRoom);
|
||||
}
|
||||
|
||||
QList<RoomEvent> receivedRoomEvents;
|
||||
Response::ResponseCode lastResponseCode = Response::RespNothing;
|
||||
|
||||
void clearReceived()
|
||||
{
|
||||
receivedRoomEvents.clear();
|
||||
lastResponseCode = Response::RespNothing;
|
||||
}
|
||||
|
||||
protected:
|
||||
void transmitProtocolItem(const ServerMessage &item) override
|
||||
{
|
||||
if (item.message_type() == ServerMessage::ROOM_EVENT) {
|
||||
receivedRoomEvents.append(item.room_event());
|
||||
} else if (item.message_type() == ServerMessage::RESPONSE) {
|
||||
lastResponseCode = item.response().response_code();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RoomChannelTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
MockDatabaseInterface db;
|
||||
FakeServer server{&db};
|
||||
QList<ServerInfo_RoomChannel> catalog;
|
||||
Server_Room *room = nullptr;
|
||||
TestRoomHandler mod{&server, &db};
|
||||
TestRoomHandler adminUser{&server, &db};
|
||||
TestRoomHandler user{&server, &db};
|
||||
ServerInfo_User moderator;
|
||||
ServerInfo_User admin;
|
||||
ServerInfo_User plainUser;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
ServerInfo_RoomChannel lobby;
|
||||
lobby.set_id("lobby");
|
||||
lobby.set_display_name("Lobby");
|
||||
lobby.set_access_level(ServerInfo_RoomChannel::Public);
|
||||
catalog.append(lobby);
|
||||
|
||||
ServerInfo_RoomChannel mods;
|
||||
mods.set_id("mods");
|
||||
mods.set_display_name("Moderators");
|
||||
mods.set_access_level(ServerInfo_RoomChannel::Moderator);
|
||||
catalog.append(mods);
|
||||
|
||||
ServerInfo_RoomChannel ops;
|
||||
ops.set_id("ops");
|
||||
ops.set_display_name("Admins");
|
||||
ops.set_access_level(ServerInfo_RoomChannel::Admin);
|
||||
catalog.append(ops);
|
||||
|
||||
room = new Server_Room(1, 1000, "test room", "", "", "", false, "Welcome", QStringList(), &server, catalog);
|
||||
|
||||
moderator.set_name("mod");
|
||||
moderator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered |
|
||||
ServerInfo_User::IsModerator);
|
||||
// Mirrors the database layer: the admin bit grants IsAdmin alongside
|
||||
// IsModerator, so admins reach moderator channels via the moderator flag.
|
||||
admin.set_name("admin");
|
||||
admin.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsAdmin |
|
||||
ServerInfo_User::IsModerator);
|
||||
plainUser.set_name("user");
|
||||
plainUser.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete room;
|
||||
}
|
||||
|
||||
// Make @p handler a member of the room directly, without history replay or JOIN_ROOM bookkeeping.
|
||||
void joinMember(TestRoomHandler &handler, const ServerInfo_User &userInfo_)
|
||||
{
|
||||
handler.authenticate(userInfo_);
|
||||
handler.attachToRoom(room);
|
||||
room->addClient(&handler);
|
||||
}
|
||||
|
||||
void sendMainSay(TestRoomHandler &handler, const QString &message)
|
||||
{
|
||||
CommandContainer cont;
|
||||
cont.set_cmd_id(1);
|
||||
cont.set_room_id(room->getId());
|
||||
Command_RoomSay cmd;
|
||||
cmd.set_message(message.toStdString());
|
||||
cont.add_room_command()->MutableExtension(Command_RoomSay::ext)->CopyFrom(cmd);
|
||||
handler.processCommandContainer(cont);
|
||||
}
|
||||
|
||||
void sendChannelSay(TestRoomHandler &handler, const QString &channelId, const QString &message)
|
||||
{
|
||||
CommandContainer cont;
|
||||
cont.set_cmd_id(1);
|
||||
cont.set_room_id(room->getId());
|
||||
Command_RoomChannelSay cmd;
|
||||
cmd.set_channel_id(channelId.toStdString());
|
||||
cmd.set_message(message.toStdString());
|
||||
cont.add_room_command()->MutableExtension(Command_RoomChannelSay::ext)->CopyFrom(cmd);
|
||||
handler.processCommandContainer(cont);
|
||||
}
|
||||
|
||||
void joinThroughCommand(TestRoomHandler &handler)
|
||||
{
|
||||
CommandContainer cont;
|
||||
cont.set_cmd_id(1);
|
||||
Command_JoinRoom cmd;
|
||||
cmd.set_room_id(room->getId());
|
||||
cont.add_session_command()->MutableExtension(Command_JoinRoom::ext)->CopyFrom(cmd);
|
||||
handler.processCommandContainer(cont);
|
||||
}
|
||||
|
||||
static QList<RoomEvent> channelSayEvents(const TestRoomHandler &handler)
|
||||
{
|
||||
QList<RoomEvent> result;
|
||||
for (const RoomEvent &event : handler.receivedRoomEvents) {
|
||||
if (static_cast<RoomEvent::RoomEventType>(getPbExtension(event)) == RoomEvent::ROOM_CHANNEL_SAY) {
|
||||
result.append(event);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QList<RoomEvent> roomSayEvents(const TestRoomHandler &handler)
|
||||
{
|
||||
QList<RoomEvent> result;
|
||||
for (const RoomEvent &event : handler.receivedRoomEvents) {
|
||||
if (static_cast<RoomEvent::RoomEventType>(getPbExtension(event)) == RoomEvent::ROOM_SAY) {
|
||||
result.append(event);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString channelIdOf(const RoomEvent &event)
|
||||
{
|
||||
return QString::fromStdString(event.GetExtension(Event_RoomChannelSay::ext).channel_id());
|
||||
}
|
||||
|
||||
static QString messageOf(const RoomEvent &event)
|
||||
{
|
||||
return QString::fromStdString(event.GetExtension(Event_RoomChannelSay::ext).message());
|
||||
}
|
||||
|
||||
static QString mainMessageOf(const RoomEvent &event)
|
||||
{
|
||||
return QString::fromStdString(event.GetExtension(Event_RoomSay::ext).message());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RoomChannelTest, MainSayStillBroadcastsToAllRoomMembers)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(user, plainUser);
|
||||
mod.clearReceived();
|
||||
user.clearReceived();
|
||||
|
||||
sendMainSay(user, "hello everyone");
|
||||
|
||||
EXPECT_EQ(user.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(roomSayEvents(mod).size(), 1);
|
||||
EXPECT_EQ(mainMessageOf(roomSayEvents(mod).first()), "hello everyone");
|
||||
EXPECT_EQ(roomSayEvents(user).size(), 1);
|
||||
EXPECT_EQ(mainMessageOf(roomSayEvents(user).first()), "hello everyone");
|
||||
EXPECT_TRUE(channelSayEvents(mod).isEmpty());
|
||||
EXPECT_TRUE(channelSayEvents(user).isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, PublicChannelSaysBroadcastToEveryone)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(user, plainUser);
|
||||
mod.clearReceived();
|
||||
user.clearReceived();
|
||||
|
||||
sendChannelSay(user, "lobby", "is there a game tonight?");
|
||||
|
||||
EXPECT_EQ(user.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(channelSayEvents(mod).size(), 1);
|
||||
EXPECT_EQ(channelIdOf(channelSayEvents(mod).first()), "lobby");
|
||||
EXPECT_EQ(messageOf(channelSayEvents(mod).first()), "is there a game tonight?");
|
||||
EXPECT_EQ(channelSayEvents(user).size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, ModeratorChannelReachesModeratorAndAbove)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(adminUser, admin);
|
||||
joinMember(user, plainUser);
|
||||
mod.clearReceived();
|
||||
adminUser.clearReceived();
|
||||
user.clearReceived();
|
||||
|
||||
sendChannelSay(mod, "mods", "quiet channel");
|
||||
|
||||
EXPECT_EQ(mod.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(channelSayEvents(mod).size(), 1);
|
||||
EXPECT_EQ(channelIdOf(channelSayEvents(mod).first()), "mods");
|
||||
// An admin holds the moderator flag alongside the admin flag and so also receives the channel message.
|
||||
EXPECT_EQ(channelSayEvents(adminUser).size(), 1);
|
||||
EXPECT_TRUE(channelSayEvents(user).isEmpty());
|
||||
EXPECT_TRUE(roomSayEvents(user).isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, AdminReachesModeratorChannel)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(adminUser, admin);
|
||||
mod.clearReceived();
|
||||
adminUser.clearReceived();
|
||||
|
||||
sendChannelSay(adminUser, "mods", "admin checking in");
|
||||
|
||||
EXPECT_EQ(adminUser.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(channelSayEvents(mod).size(), 1);
|
||||
EXPECT_EQ(channelSayEvents(adminUser).size(), 1);
|
||||
EXPECT_EQ(messageOf(channelSayEvents(mod).first()), "admin checking in");
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, UserBelowRequiredLevelCannotSendToChannel)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(user, plainUser);
|
||||
mod.clearReceived();
|
||||
user.clearReceived();
|
||||
|
||||
sendChannelSay(user, "mods", "let me in");
|
||||
|
||||
EXPECT_EQ(user.lastResponseCode, Response::RespFunctionNotAllowed);
|
||||
EXPECT_TRUE(channelSayEvents(mod).isEmpty());
|
||||
EXPECT_TRUE(channelSayEvents(user).isEmpty());
|
||||
bool loggedAsChannel = false;
|
||||
for (const MockDatabaseInterface::LogEntry &entry : db.logEntries) {
|
||||
loggedAsChannel |= entry.targetType == Server_DatabaseInterface::MessageTargetRoomChannel;
|
||||
}
|
||||
EXPECT_FALSE(loggedAsChannel);
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, AdminChannelRejectsModerator)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(adminUser, admin);
|
||||
mod.clearReceived();
|
||||
adminUser.clearReceived();
|
||||
|
||||
sendChannelSay(mod, "ops", "not allowed");
|
||||
|
||||
EXPECT_EQ(mod.lastResponseCode, Response::RespFunctionNotAllowed);
|
||||
EXPECT_TRUE(channelSayEvents(mod).isEmpty());
|
||||
EXPECT_TRUE(channelSayEvents(adminUser).isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, AdminChannelReachesAdminsOnly)
|
||||
{
|
||||
joinMember(mod, moderator);
|
||||
joinMember(adminUser, admin);
|
||||
joinMember(user, plainUser);
|
||||
mod.clearReceived();
|
||||
adminUser.clearReceived();
|
||||
user.clearReceived();
|
||||
|
||||
sendChannelSay(adminUser, "ops", "staff only");
|
||||
|
||||
EXPECT_EQ(adminUser.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(channelSayEvents(adminUser).size(), 1);
|
||||
EXPECT_TRUE(channelSayEvents(mod).isEmpty());
|
||||
EXPECT_TRUE(channelSayEvents(user).isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, UnknownChannelIdIsRejected)
|
||||
{
|
||||
joinMember(user, plainUser);
|
||||
user.clearReceived();
|
||||
|
||||
sendChannelSay(user, "not-a-channel", "spam");
|
||||
|
||||
EXPECT_EQ(user.lastResponseCode, Response::RespInvalidCommand);
|
||||
EXPECT_TRUE(channelSayEvents(user).isEmpty());
|
||||
EXPECT_TRUE(db.logEntries.isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, MissingAccessLevelDefaultsToPublic)
|
||||
{
|
||||
ServerInfo_RoomChannel defaultAccess;
|
||||
defaultAccess.set_id("public-by-default");
|
||||
defaultAccess.set_display_name("Default Access");
|
||||
catalog.append(defaultAccess);
|
||||
auto *secondRoom = new Server_Room(2, 1000, "second", "", "", "", false, "", QStringList(), &server, catalog);
|
||||
|
||||
TestRoomHandler joiner{&server, &db};
|
||||
joiner.authenticate(plainUser);
|
||||
joiner.attachToRoom(secondRoom);
|
||||
secondRoom->addClient(&joiner);
|
||||
|
||||
CommandContainer cont;
|
||||
cont.set_cmd_id(1);
|
||||
cont.set_room_id(2);
|
||||
Command_RoomChannelSay cmd;
|
||||
cmd.set_channel_id("public-by-default");
|
||||
cmd.set_message("hi");
|
||||
cont.add_room_command()->MutableExtension(Command_RoomChannelSay::ext)->CopyFrom(cmd);
|
||||
joiner.processCommandContainer(cont);
|
||||
|
||||
EXPECT_EQ(joiner.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(channelSayEvents(joiner).size(), 1);
|
||||
|
||||
delete secondRoom;
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, HistoryStoredPerChannel)
|
||||
{
|
||||
room->say("user", "main message", QString(), false);
|
||||
room->say("user", "lobby message", "lobby", false);
|
||||
room->say("user", "mods message", "mods", false);
|
||||
|
||||
const QList<ServerInfo_ChatMessage> mainHistory = room->getChatHistory(Server_Room::MAIN_CHANNEL_KEY);
|
||||
EXPECT_EQ(mainHistory.size(), 1);
|
||||
EXPECT_EQ(QString::fromStdString(mainHistory.first().message()), "main message");
|
||||
EXPECT_TRUE(QString::fromStdString(mainHistory.first().channel()).isEmpty());
|
||||
|
||||
const QList<ServerInfo_ChatMessage> lobbyHistory = room->getChatHistory("lobby");
|
||||
EXPECT_EQ(lobbyHistory.size(), 1);
|
||||
EXPECT_EQ(QString::fromStdString(lobbyHistory.first().message()), "lobby message");
|
||||
EXPECT_EQ(QString::fromStdString(lobbyHistory.first().channel()), "lobby");
|
||||
|
||||
const QList<ServerInfo_ChatMessage> modsHistory = room->getChatHistory("mods");
|
||||
EXPECT_EQ(modsHistory.size(), 1);
|
||||
EXPECT_EQ(QString::fromStdString(modsHistory.first().message()), "mods message");
|
||||
EXPECT_EQ(QString::fromStdString(modsHistory.first().channel()), "mods");
|
||||
|
||||
// A channel id maps to its own history bucket, never to the main one.
|
||||
EXPECT_EQ(room->getChatHistory("").size(), mainHistory.size());
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, JoinReplaysOnlyVisibleChannelHistory)
|
||||
{
|
||||
server.insertRoom(room);
|
||||
joinMember(mod, moderator);
|
||||
joinMember(user, plainUser);
|
||||
|
||||
room->say("user", "main message", QString(), false);
|
||||
room->say("user", "lobby message", "lobby", false);
|
||||
room->say("mod", "mods message", "mods", false);
|
||||
room->say("admin", "ops message", "ops", false);
|
||||
|
||||
TestRoomHandler lateUser{&server, &db};
|
||||
lateUser.authenticate(plainUser);
|
||||
joinThroughCommand(lateUser);
|
||||
|
||||
EXPECT_EQ(lateUser.lastResponseCode, Response::RespOk);
|
||||
const QList<RoomEvent> lateChannelEvents = channelSayEvents(lateUser);
|
||||
// Replay: one main history event, one public channel history event, the welcome message -- but no restricted
|
||||
// history.
|
||||
EXPECT_EQ(roomSayEvents(lateUser).size(), 2);
|
||||
EXPECT_EQ(lateChannelEvents.size(), 1);
|
||||
EXPECT_EQ(channelIdOf(lateChannelEvents.first()), "lobby");
|
||||
|
||||
TestRoomHandler lateMod{&server, &db};
|
||||
lateMod.authenticate(moderator);
|
||||
joinThroughCommand(lateMod);
|
||||
|
||||
EXPECT_EQ(lateMod.lastResponseCode, Response::RespOk);
|
||||
const QList<RoomEvent> lateModChannelEvents = channelSayEvents(lateMod);
|
||||
EXPECT_EQ(lateModChannelEvents.size(), 2);
|
||||
EXPECT_EQ(channelIdOf(lateModChannelEvents.at(0)), "lobby");
|
||||
EXPECT_EQ(channelIdOf(lateModChannelEvents.at(1)), "mods");
|
||||
|
||||
TestRoomHandler lateAdmin{&server, &db};
|
||||
lateAdmin.authenticate(admin);
|
||||
joinThroughCommand(lateAdmin);
|
||||
|
||||
EXPECT_EQ(lateAdmin.lastResponseCode, Response::RespOk);
|
||||
const QList<RoomEvent> lateAdminChannelEvents = channelSayEvents(lateAdmin);
|
||||
// An admin sees the moderator channel and the admin channel on top of the public one.
|
||||
EXPECT_EQ(lateAdminChannelEvents.size(), 3);
|
||||
EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(0)), "lobby");
|
||||
EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(1)), "mods");
|
||||
EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(2)), "ops");
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, AuditLoggingTagsChannelMessages)
|
||||
{
|
||||
joinMember(user, plainUser);
|
||||
|
||||
sendMainSay(user, "main log line");
|
||||
sendChannelSay(user, "lobby", "channel log line");
|
||||
|
||||
ASSERT_EQ(db.logEntries.size(), 2);
|
||||
EXPECT_EQ(db.logEntries.at(0).targetType, Server_DatabaseInterface::MessageTargetRoom);
|
||||
EXPECT_TRUE(db.logEntries.at(0).channel.isEmpty());
|
||||
EXPECT_EQ(db.logEntries.at(1).targetType, Server_DatabaseInterface::MessageTargetRoomChannel);
|
||||
EXPECT_EQ(db.logEntries.at(1).channel, "lobby");
|
||||
EXPECT_EQ(db.logEntries.at(1).senderName, "user");
|
||||
EXPECT_EQ(db.logEntries.at(1).message, "channel log line");
|
||||
}
|
||||
|
||||
TEST_F(RoomChannelTest, PublicChannelRelaysOverIslButRestrictedChannelsDoNot)
|
||||
{
|
||||
QList<IslMessage> relMessages;
|
||||
QObject::connect(&server, &Server::sigSendIslMessage,
|
||||
[&relMessages](const IslMessage &message, int) { relMessages.append(message); });
|
||||
|
||||
room->say("mod", "public msg", "lobby");
|
||||
room->say("mod", "mods msg", "mods");
|
||||
room->say("admin", "ops msg", "ops");
|
||||
|
||||
ASSERT_EQ(relMessages.size(), 1);
|
||||
const RoomEvent &relayed = relMessages.first().room_event();
|
||||
EXPECT_EQ(static_cast<RoomEvent::RoomEventType>(getPbExtension(relayed)), RoomEvent::ROOM_CHANNEL_SAY);
|
||||
EXPECT_EQ(channelIdOf(relayed), "lobby");
|
||||
EXPECT_EQ(messageOf(relayed), "public msg");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue