mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-23 10:05:10 -07:00
[Server/Client/Protocol] Address developer role review feedback
Address ZeizaZach's review of the developer staff role: - Nudge the developer log query to exclude private chat and sender IPs (the ModeratorCommand path still sees everything). - Deduplicate Command_GetLogHistory into Command_ViewLogHistory, which now extends both ModeratorCommand (ext) and DeveloperCommand (dev_ext); the client picks the DeveloperCommand-scoped extension by extendee, and the server reads it via the extension number. - Pull the uptime snapshot SQL into Servatrice_DatabaseInterface as getLatestUptimeSnapshot() and widen the reported counters to 64-bit. - Document the admin bitfield (1 admin, 2 moderator, 4 judge, 8 developer) and add a server-side test for the developer command path.
This commit is contained in:
parent
1ff7ffcaa4
commit
1dc20176b3
14 changed files with 251 additions and 76 deletions
|
|
@ -94,7 +94,7 @@ void TabDeveloper::refreshClicked()
|
||||||
void TabDeveloper::serverStatsResponse(const Response &resp)
|
void TabDeveloper::serverStatsResponse(const Response &resp)
|
||||||
{
|
{
|
||||||
if (resp.response_code() != Response::RespOk) {
|
if (resp.response_code() != Response::RespOk) {
|
||||||
statusLabel->setText(tr("Failed to collect server statistics."));
|
statusLabel->setText(tr("No server statistics available yet."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||||
#include <libcockatrice/protocol/pb/command_get_log_history.pb.h>
|
|
||||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_viewlog_history.pb.h>
|
#include <libcockatrice/protocol/pb/response_viewlog_history.pb.h>
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
#include <libcockatrice/protocol/pending_command.h>
|
||||||
|
|
@ -82,7 +81,9 @@ void TabLog::getClicked()
|
||||||
if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) {
|
if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) {
|
||||||
mainRoom->setChecked(true);
|
mainRoom->setChecked(true);
|
||||||
gameRoom->setChecked(true);
|
gameRoom->setChecked(true);
|
||||||
privateChat->setChecked(true);
|
if (!canUseDeveloperCommands) {
|
||||||
|
privateChat->setChecked(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maximumResults->value() == 0) {
|
if (maximumResults->value() == 0) {
|
||||||
|
|
@ -123,18 +124,7 @@ void TabLog::getClicked()
|
||||||
PendingCommand *pend;
|
PendingCommand *pend;
|
||||||
if (canUseDeveloperCommands) {
|
if (canUseDeveloperCommands) {
|
||||||
// Developers query logs through the developer command family.
|
// Developers query logs through the developer command family.
|
||||||
Command_GetLogHistory devCmd;
|
pend = client->prepareDeveloperCommand(cmd);
|
||||||
devCmd.set_user_name(cmd.user_name());
|
|
||||||
devCmd.set_ip_address(cmd.ip_address());
|
|
||||||
devCmd.set_game_name(cmd.game_name());
|
|
||||||
devCmd.set_game_id(cmd.game_id());
|
|
||||||
devCmd.set_message(cmd.message());
|
|
||||||
for (int i = 0; i < cmd.log_location_size(); ++i) {
|
|
||||||
devCmd.add_log_location(cmd.log_location(i));
|
|
||||||
}
|
|
||||||
devCmd.set_date_range(cmd.date_range());
|
|
||||||
devCmd.set_maximum_results(cmd.maximum_results());
|
|
||||||
pend = client->prepareDeveloperCommand(devCmd);
|
|
||||||
} else {
|
} else {
|
||||||
pend = client->prepareModeratorCommand(cmd);
|
pend = client->prepareModeratorCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +182,10 @@ void TabLog::createDock()
|
||||||
mainRoom = new QCheckBox(tr("Main Room"));
|
mainRoom = new QCheckBox(tr("Main Room"));
|
||||||
gameRoom = new QCheckBox(tr("Game Room"));
|
gameRoom = new QCheckBox(tr("Game Room"));
|
||||||
privateChat = new QCheckBox(tr("Private Chat"));
|
privateChat = new QCheckBox(tr("Private Chat"));
|
||||||
|
if (canUseDeveloperCommands) {
|
||||||
|
// Developers cannot query private conversations.
|
||||||
|
privateChat->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
pastDays = new QRadioButton(tr("Past X Days: "));
|
pastDays = new QRadioButton(tr("Past X Days: "));
|
||||||
today = new QRadioButton(tr("Today"));
|
today = new QRadioButton(tr("Today"));
|
||||||
|
|
|
||||||
|
|
@ -844,10 +844,10 @@ void TabSupervisor::actTabLog(bool checked)
|
||||||
|
|
||||||
void TabSupervisor::openTabLog()
|
void TabSupervisor::openTabLog()
|
||||||
{
|
{
|
||||||
// Developers without moderation rights query logs through the developer
|
// Developers query logs through the developer command family, so tell the
|
||||||
// command family, so tell the tab which family to use.
|
// tab which family to use.
|
||||||
const bool isDeveloper = (userInfo->user_level() & ServerInfo_User::IsDeveloper) != 0;
|
const bool useDeveloperCommands = (userInfo->user_level() & ServerInfo_User::IsDeveloper) != 0;
|
||||||
tabLog = new TabLog(this, client, isDeveloper);
|
tabLog = new TabLog(this, client, useDeveloperCommands);
|
||||||
myAddTab(tabLog, aTabLog);
|
myAddTab(tabLog, aTabLog);
|
||||||
connect(tabLog, &QObject::destroyed, this, [this] {
|
connect(tabLog, &QObject::destroyed, this, [this] {
|
||||||
tabLog = nullptr;
|
tabLog = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,19 @@ PendingCommand *AbstractClient::prepareDeveloperCommand(const ::google::protobuf
|
||||||
{
|
{
|
||||||
CommandContainer cont;
|
CommandContainer cont;
|
||||||
DeveloperCommand *c = cont.add_developer_command();
|
DeveloperCommand *c = cont.add_developer_command();
|
||||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
// A developer command message may also be usable through other command
|
||||||
|
// families, so select the extension scoped to DeveloperCommand rather than
|
||||||
|
// guessing by name.
|
||||||
|
const ::google::protobuf::Descriptor *cmdDescriptor = cmd.GetDescriptor();
|
||||||
|
const ::google::protobuf::Descriptor *developerDescriptor = DeveloperCommand::descriptor();
|
||||||
|
const ::google::protobuf::FieldDescriptor *developerExtension = nullptr;
|
||||||
|
for (int i = 0; i < cmdDescriptor->extension_count(); ++i) {
|
||||||
|
if (cmdDescriptor->extension(i)->containing_type() == developerDescriptor) {
|
||||||
|
developerExtension = cmdDescriptor->extension(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Q_ASSERT(developerExtension != nullptr);
|
||||||
|
c->GetReflection()->MutableMessage(c, developerExtension)->CopyFrom(cmd);
|
||||||
return new PendingCommand(cont);
|
return new PendingCommand(cont);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,10 @@ set(PROTO_FILES
|
||||||
command_del_counter.proto
|
command_del_counter.proto
|
||||||
command_delete_arrow.proto
|
command_delete_arrow.proto
|
||||||
command_draw_cards.proto
|
command_draw_cards.proto
|
||||||
command_get_log_history.proto
|
|
||||||
command_get_server_stats.proto
|
|
||||||
command_dump_zone.proto
|
command_dump_zone.proto
|
||||||
command_flip_card.proto
|
command_flip_card.proto
|
||||||
command_game_say.proto
|
command_game_say.proto
|
||||||
|
command_get_server_stats.proto
|
||||||
command_inc_card_counter.proto
|
command_inc_card_counter.proto
|
||||||
command_inc_counter.proto
|
command_inc_counter.proto
|
||||||
command_kick_from_game.proto
|
command_kick_from_game.proto
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
syntax = "proto2";
|
|
||||||
import "developer_commands.proto";
|
|
||||||
|
|
||||||
// Developer counterpart of Command_ViewLogHistory: identical query fields, but
|
|
||||||
// routed through the developer command family so developers never need the
|
|
||||||
// moderator command container.
|
|
||||||
message Command_GetLogHistory {
|
|
||||||
extend DeveloperCommand {
|
|
||||||
optional Command_GetLogHistory ext = 1001;
|
|
||||||
}
|
|
||||||
optional string user_name = 1; // user that created message
|
|
||||||
optional string ip_address = 2; // ip address of user that created message
|
|
||||||
optional string game_name = 3; // client id of user that created the message
|
|
||||||
optional string game_id = 4; // game number the message was sent to
|
|
||||||
optional string message = 5; // raw message that was sent
|
|
||||||
repeated string log_location = 6; // destination of message (ex: main room, game room, private chat)
|
|
||||||
required uint32 date_range = 7; // the length of time (in minutes) to look back for
|
|
||||||
optional uint32 maximum_results = 8; // the maximum number of query results
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
|
import "developer_commands.proto";
|
||||||
message ModeratorCommand {
|
message ModeratorCommand {
|
||||||
enum ModeratorCommandType {
|
enum ModeratorCommandType {
|
||||||
BAN_FROM_SERVER = 1000;
|
BAN_FROM_SERVER = 1000;
|
||||||
|
|
@ -80,6 +81,9 @@ message Command_ViewLogHistory {
|
||||||
extend ModeratorCommand {
|
extend ModeratorCommand {
|
||||||
optional Command_ViewLogHistory ext = 1005;
|
optional Command_ViewLogHistory ext = 1005;
|
||||||
}
|
}
|
||||||
|
extend DeveloperCommand {
|
||||||
|
optional Command_ViewLogHistory dev_ext = 1001;
|
||||||
|
}
|
||||||
optional string user_name = 1; // user that created message
|
optional string user_name = 1; // user that created message
|
||||||
optional string ip_address = 2; // ip address of user that created message
|
optional string ip_address = 2; // ip address of user that created message
|
||||||
optional string game_name = 3; // client id of user that created the message
|
optional string game_name = 3; // client id of user that created the message
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ INSERT INTO cockatrice_schema_version VALUES(36);
|
||||||
-- users and user data tables
|
-- users and user data tables
|
||||||
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||||
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
||||||
|
-- Bitfield of staff levels: 1 = admin (implies moderator), 2 = moderator,
|
||||||
|
-- 4 = judge, 8 = developer. Operators set these by hand with
|
||||||
|
-- "UPDATE cockatrice_users SET admin = ...".
|
||||||
`admin` tinyint(1) NOT NULL,
|
`admin` tinyint(1) NOT NULL,
|
||||||
`name` varchar(35) NOT NULL,
|
`name` varchar(35) NOT NULL,
|
||||||
`realname` varchar(255) NOT NULL,
|
`realname` varchar(255) NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -1487,6 +1487,38 @@ QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastL
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Servatrice_DatabaseInterface::UptimeSnapshot Servatrice_DatabaseInterface::getLatestUptimeSnapshot(int serverId)
|
||||||
|
{
|
||||||
|
UptimeSnapshot snapshot;
|
||||||
|
|
||||||
|
if (!checkSql()) {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *query = prepareQuery("SELECT users_count, mods_count, games_count, tx_bytes, rx_bytes, uptime, "
|
||||||
|
"UNIX_TIMESTAMP(timest) FROM {prefix}_uptime "
|
||||||
|
"WHERE id_server = :id_server ORDER BY timest DESC LIMIT 1");
|
||||||
|
query->bindValue(":id_server", serverId);
|
||||||
|
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
qCWarning(DatabaseInterfaceLog) << "Failed to collect server stats snapshot: SQL Error";
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query->next()) {
|
||||||
|
snapshot.valid = true;
|
||||||
|
snapshot.usersCount = query->value(0).toULongLong();
|
||||||
|
snapshot.modsCount = query->value(1).toULongLong();
|
||||||
|
snapshot.gamesCount = query->value(2).toULongLong();
|
||||||
|
snapshot.txBytes = query->value(3).toULongLong();
|
||||||
|
snapshot.rxBytes = query->value(4).toULongLong();
|
||||||
|
snapshot.uptimeSecs = query->value(5).toULongLong();
|
||||||
|
snapshot.timest = query->value(6).toULongLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
|
bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
|
||||||
{
|
{
|
||||||
if (!checkSql()) {
|
if (!checkSql()) {
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,21 @@ public:
|
||||||
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
|
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
|
||||||
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
|
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
|
||||||
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
||||||
|
|
||||||
|
// Uptime snapshot as recorded by Servatrice::statusUpdate() into the
|
||||||
|
// {prefix}_uptime table. valid is false when no snapshot exists yet.
|
||||||
|
struct UptimeSnapshot
|
||||||
|
{
|
||||||
|
bool valid = false;
|
||||||
|
quint64 usersCount = 0;
|
||||||
|
quint64 modsCount = 0;
|
||||||
|
quint64 gamesCount = 0;
|
||||||
|
quint64 txBytes = 0;
|
||||||
|
quint64 rxBytes = 0;
|
||||||
|
quint64 uptimeSecs = 0;
|
||||||
|
quint64 timest = 0;
|
||||||
|
};
|
||||||
|
UptimeSnapshot getLatestUptimeSnapshot(int serverId);
|
||||||
bool removeUserAvatar(const QString &userName);
|
bool removeUserAvatar(const QString &userName);
|
||||||
bool addForgotPassword(const QString &user);
|
bool addForgotPassword(const QString &user);
|
||||||
bool removeForgotPassword(const QString &user) override;
|
bool removeForgotPassword(const QString &user) override;
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,7 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
||||||
case ModeratorCommand::REPORT_RESOLVE:
|
case ModeratorCommand::REPORT_RESOLVE:
|
||||||
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
|
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
|
||||||
case ModeratorCommand::VIEWLOG_HISTORY:
|
case ModeratorCommand::VIEWLOG_HISTORY:
|
||||||
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc);
|
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true);
|
||||||
case ModeratorCommand::GRANT_REPLAY_ACCESS:
|
case ModeratorCommand::GRANT_REPLAY_ACCESS:
|
||||||
return cmdGrantReplayAccess(cmd.GetExtension(Command_GrantReplayAccess::ext), rc);
|
return cmdGrantReplayAccess(cmd.GetExtension(Command_GrantReplayAccess::ext), rc);
|
||||||
case ModeratorCommand::REPLAY_DOWNLOAD_BY_GAME_ID:
|
case ModeratorCommand::REPLAY_DOWNLOAD_BY_GAME_ID:
|
||||||
|
|
@ -351,21 +351,9 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedDeveloperCo
|
||||||
case DeveloperCommand::GET_SERVER_STATS:
|
case DeveloperCommand::GET_SERVER_STATS:
|
||||||
return cmdGetServerStats(cmd.GetExtension(Command_GetServerStats::ext), rc);
|
return cmdGetServerStats(cmd.GetExtension(Command_GetServerStats::ext), rc);
|
||||||
case DeveloperCommand::VIEWLOG_HISTORY: {
|
case DeveloperCommand::VIEWLOG_HISTORY: {
|
||||||
// Same query as the moderator log view, just carried by the
|
// Same query as the moderator log view, carried by the developer
|
||||||
// developer command family.
|
// command family, but narrows out private chats and sender IPs.
|
||||||
const Command_GetLogHistory &devCmd = cmd.GetExtension(Command_GetLogHistory::ext);
|
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::dev_ext), rc, false);
|
||||||
Command_ViewLogHistory modCmd;
|
|
||||||
modCmd.set_user_name(devCmd.user_name());
|
|
||||||
modCmd.set_ip_address(devCmd.ip_address());
|
|
||||||
modCmd.set_game_name(devCmd.game_name());
|
|
||||||
modCmd.set_game_id(devCmd.game_id());
|
|
||||||
modCmd.set_message(devCmd.message());
|
|
||||||
for (int i = 0; i < devCmd.log_location_size(); ++i) {
|
|
||||||
modCmd.add_log_location(devCmd.log_location(i));
|
|
||||||
}
|
|
||||||
modCmd.set_date_range(devCmd.date_range());
|
|
||||||
modCmd.set_maximum_results(devCmd.maximum_results());
|
|
||||||
return cmdGetLogHistory(modCmd, rc);
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
|
|
@ -1055,12 +1043,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const
|
||||||
// MODERATOR FUNCTIONS.
|
// MODERATOR FUNCTIONS.
|
||||||
// May be called by admins and moderators. Permission is checked by the calling function.
|
// May be called by admins and moderators. Permission is checked by the calling function.
|
||||||
Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Command_ViewLogHistory &cmd,
|
Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Command_ViewLogHistory &cmd,
|
||||||
ResponseContainer &rc)
|
ResponseContainer &rc,
|
||||||
|
bool allowPrivateChat)
|
||||||
{
|
{
|
||||||
|
|
||||||
QList<ServerInfo_ChatMessage> messageList;
|
QList<ServerInfo_ChatMessage> messageList;
|
||||||
QString userName = nameFromStdString(cmd.user_name());
|
QString userName = nameFromStdString(cmd.user_name());
|
||||||
QString ipAddress = nameFromStdString(cmd.ip_address());
|
QString ipAddress = allowPrivateChat ? nameFromStdString(cmd.ip_address()) : QString();
|
||||||
QString gameName = nameFromStdString(cmd.game_name());
|
QString gameName = nameFromStdString(cmd.game_name());
|
||||||
QString gameID = nameFromStdString(cmd.game_id());
|
QString gameID = nameFromStdString(cmd.game_id());
|
||||||
QString message = textFromStdString(cmd.message());
|
QString message = textFromStdString(cmd.message());
|
||||||
|
|
@ -1075,7 +1064,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
||||||
if (nameFromStdString(cmd.log_location(i)).simplified() == "game") {
|
if (nameFromStdString(cmd.log_location(i)).simplified() == "game") {
|
||||||
gameType = true;
|
gameType = true;
|
||||||
}
|
}
|
||||||
if (nameFromStdString(cmd.log_location(i)).simplified() == "chat") {
|
if (nameFromStdString(cmd.log_location(i)).simplified() == "chat" && allowPrivateChat) {
|
||||||
chatType = true;
|
chatType = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1089,7 +1078,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
||||||
QListIterator<ServerInfo_ChatMessage> messageIterator(sqlInterface->getMessageLogHistory(
|
QListIterator<ServerInfo_ChatMessage> messageIterator(sqlInterface->getMessageLogHistory(
|
||||||
userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults));
|
userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults));
|
||||||
while (messageIterator.hasNext()) {
|
while (messageIterator.hasNext()) {
|
||||||
re->add_log_message()->CopyFrom(messageIterator.next());
|
ServerInfo_ChatMessage chatMessage = messageIterator.next();
|
||||||
|
if (!allowPrivateChat) {
|
||||||
|
chatMessage.clear_sender_ip();
|
||||||
|
}
|
||||||
|
re->add_log_message()->CopyFrom(chatMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ServerInfo_ChatMessage chatMessage;
|
ServerInfo_ChatMessage chatMessage;
|
||||||
|
|
@ -1687,24 +1680,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetServerStats(const Co
|
||||||
|
|
||||||
// Servatrice::statusUpdate() periodically snapshots server health into the
|
// Servatrice::statusUpdate() periodically snapshots server health into the
|
||||||
// uptime table. Serve the freshest snapshot for this server.
|
// uptime table. Serve the freshest snapshot for this server.
|
||||||
QSqlQuery *query = sqlInterface->prepareQuery(
|
const auto snapshot = sqlInterface->getLatestUptimeSnapshot(servatrice->getServerID());
|
||||||
"SELECT users_count, mods_count, games_count, tx_bytes, rx_bytes, uptime, UNIX_TIMESTAMP(timest) "
|
if (!snapshot.valid) {
|
||||||
"FROM {prefix}_uptime WHERE id_server = :id_server ORDER BY timest DESC LIMIT 1");
|
// No snapshot yet (fresh server, or statusUpdate() has not ticked).
|
||||||
query->bindValue(":id_server", servatrice->getServerID());
|
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
|
||||||
return Response::RespInternalError;
|
return Response::RespInternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *re = new Response_GetServerStats;
|
auto *re = new Response_GetServerStats;
|
||||||
if (query->next()) {
|
re->set_users_count(snapshot.usersCount);
|
||||||
re->set_users_count(query->value(0).toUInt());
|
re->set_mods_count(snapshot.modsCount);
|
||||||
re->set_mods_count(query->value(1).toUInt());
|
re->set_games_count(snapshot.gamesCount);
|
||||||
re->set_games_count(query->value(2).toUInt());
|
re->set_tx_bytes(snapshot.txBytes);
|
||||||
re->set_tx_bytes(query->value(3).toUInt());
|
re->set_rx_bytes(snapshot.rxBytes);
|
||||||
re->set_rx_bytes(query->value(4).toUInt());
|
re->set_uptime_secs(snapshot.uptimeSecs);
|
||||||
re->set_uptime_secs(query->value(5).toUInt());
|
re->set_timest(snapshot.timest);
|
||||||
re->set_timest(query->value(6).toUInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
rc.setResponseExtension(re);
|
rc.setResponseExtension(re);
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ private:
|
||||||
Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc);
|
Response::ResponseCode
|
||||||
|
cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc, bool allowPrivateChat);
|
||||||
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdGetWarnList(const Command_GetWarnList &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdGetWarnList(const Command_GetWarnList &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ add_test(NAME playmat_resolver_test COMMAND playmat_resolver_test)
|
||||||
add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
|
add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
|
||||||
add_test(NAME server_counter_test COMMAND server_counter_test)
|
add_test(NAME server_counter_test COMMAND server_counter_test)
|
||||||
add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test)
|
add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test)
|
||||||
|
add_test(NAME server_developer_role_test COMMAND server_developer_role_test)
|
||||||
add_test(NAME warning_categories_test COMMAND warning_categories_test)
|
add_test(NAME warning_categories_test COMMAND warning_categories_test)
|
||||||
add_test(NAME lag_monitor_test COMMAND lag_monitor_test)
|
add_test(NAME lag_monitor_test COMMAND lag_monitor_test)
|
||||||
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
||||||
|
|
@ -30,6 +31,7 @@ add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
|
||||||
add_executable(server_card_counter_test server_card_counter_test.cpp)
|
add_executable(server_card_counter_test server_card_counter_test.cpp)
|
||||||
add_executable(server_counter_test server_counter_test.cpp)
|
add_executable(server_counter_test server_counter_test.cpp)
|
||||||
add_executable(server_rate_limiter_test server_rate_limiter_test.cpp)
|
add_executable(server_rate_limiter_test server_rate_limiter_test.cpp)
|
||||||
|
add_executable(server_developer_role_test server_developer_role_test.cpp)
|
||||||
add_executable(warning_categories_test warning_categories_test.cpp)
|
add_executable(warning_categories_test warning_categories_test.cpp)
|
||||||
add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp lag_monitor_test.cpp)
|
add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp lag_monitor_test.cpp)
|
||||||
target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||||
|
|
@ -70,6 +72,7 @@ if(NOT GTEST_FOUND)
|
||||||
add_dependencies(server_card_counter_test gtest)
|
add_dependencies(server_card_counter_test gtest)
|
||||||
add_dependencies(server_counter_test gtest)
|
add_dependencies(server_counter_test gtest)
|
||||||
add_dependencies(server_rate_limiter_test gtest)
|
add_dependencies(server_rate_limiter_test gtest)
|
||||||
|
add_dependencies(server_developer_role_test gtest)
|
||||||
add_dependencies(warning_categories_test gtest)
|
add_dependencies(warning_categories_test gtest)
|
||||||
add_dependencies(lag_monitor_test gtest)
|
add_dependencies(lag_monitor_test gtest)
|
||||||
add_dependencies(latency_tracker_test gtest)
|
add_dependencies(latency_tracker_test gtest)
|
||||||
|
|
@ -104,6 +107,10 @@ target_link_libraries(
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
server_rate_limiter_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
server_rate_limiter_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||||
)
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
server_developer_role_test libcockatrice_network libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||||
|
${TEST_QT_MODULES}
|
||||||
|
)
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
137
tests/server_developer_role_test.cpp
Normal file
137
tests/server_developer_role_test.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
/** @file server_developer_role_test.cpp
|
||||||
|
* @brief Tests for the developer staff role authorization and dispatch.
|
||||||
|
* @ingroup Tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libcockatrice/network/server/remote/server.h>
|
||||||
|
#include <libcockatrice/network/server/remote/server_protocolhandler.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_get_server_stats.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/developer_commands.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
|
|
||||||
|
// The server_remote library references the global RNG, which is normally
|
||||||
|
// defined by the servatrice/client executable main(). Provide a stub so the
|
||||||
|
// unit test can link against it.
|
||||||
|
RNG_Abstract *rng = nullptr;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
class TestDeveloperHandler : public Server_ProtocolHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TestDeveloperHandler(Server *_server) : Server_ProtocolHandler(_server, nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getAddress() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
QString getConnectionType() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer the last response code sent to the client so tests can assert on
|
||||||
|
// the outcome of processCommandContainer().
|
||||||
|
Response::ResponseCode lastResponseCode = Response::RespNothing;
|
||||||
|
int dispatchCount = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void transmitProtocolItem(const ServerMessage &item) override
|
||||||
|
{
|
||||||
|
if (item.message_type() == ServerMessage::RESPONSE) {
|
||||||
|
lastResponseCode = item.response().response_code();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
processExtendedDeveloperCommand(int cmdType, const DeveloperCommand &, ResponseContainer &) override
|
||||||
|
{
|
||||||
|
++dispatchCount;
|
||||||
|
// Fail closed for anything not explicitly handled.
|
||||||
|
if (cmdType != DeveloperCommand::GET_SERVER_STATS) {
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DeveloperRoleTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Server server;
|
||||||
|
TestDeveloperHandler handler{&server};
|
||||||
|
|
||||||
|
void setUserLevel(uint32_t level)
|
||||||
|
{
|
||||||
|
ServerInfo_User user;
|
||||||
|
user.set_user_level(level);
|
||||||
|
handler.setUserInfo(user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(DeveloperRoleTest, RejectsWhenNotLoggedIn)
|
||||||
|
{
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_developer_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeveloperRoleTest, RejectsPlainUser)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_developer_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeveloperRoleTest, RejectsModeratorThatIsNotDeveloper)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsModerator);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_developer_command();
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeveloperRoleTest, DispatchesToDeveloperCommandForDeveloper)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsDeveloper);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
DeveloperCommand *cmd = cont.add_developer_command();
|
||||||
|
cmd->MutableExtension(Command_GetServerStats::ext);
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespOk);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeveloperRoleTest, FailClosedForUnknownDeveloperCommand)
|
||||||
|
{
|
||||||
|
setUserLevel(ServerInfo_User::IsDeveloper);
|
||||||
|
|
||||||
|
CommandContainer cont;
|
||||||
|
cont.add_developer_command(); // no extension set -> getPbExtension() returns -1
|
||||||
|
handler.processCommandContainer(cont);
|
||||||
|
EXPECT_EQ(handler.lastResponseCode, Response::RespFunctionNotAllowed);
|
||||||
|
EXPECT_EQ(handler.dispatchCount, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // 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