mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-25 11:56:11 -07:00
Compare commits
5 commits
2b33cbfb66
...
ab243cc499
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab243cc499 | ||
|
|
763b5cf74d | ||
|
|
60c6f074de | ||
|
|
914f0a2aee | ||
|
|
7a18670bfd |
35 changed files with 708 additions and 27 deletions
|
|
@ -380,6 +380,7 @@ set(cockatrice_SOURCES
|
|||
src/interface/widgets/tabs/tab_card_art_rules.cpp
|
||||
src/interface/widgets/tabs/tab_deck_editor.cpp
|
||||
src/interface/widgets/tabs/tab_deck_storage.cpp
|
||||
src/interface/widgets/tabs/tab_developer.cpp
|
||||
src/interface/widgets/tabs/tab_game.cpp
|
||||
src/interface/widgets/tabs/tab_home.cpp
|
||||
src/interface/widgets/tabs/tab_logs.cpp
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff";
|
||||
#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000";
|
||||
#define DEFAULT_COLOR_ADMIN "#ff2701";
|
||||
#define DEFAULT_COLOR_DEVELOPER "#800020"
|
||||
|
||||
/**
|
||||
* Clamps an svg render size so that rendering does not exceed a multiple of the requested size.
|
||||
|
|
@ -359,6 +360,8 @@ QIcon UserLevelPixmapGenerator::generateIconDefault(int height,
|
|||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
colorLeft = DEFAULT_COLOR_ADMIN;
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
colorLeft = DEFAULT_COLOR_DEVELOPER;
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
colorLeft = DEFAULT_COLOR_MODERATOR_LEFT;
|
||||
colorRight = DEFAULT_COLOR_MODERATOR_RIGHT;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ UserContextMenu::UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *parent,
|
|||
aDemoteFromMod = new QAction(QString(), this);
|
||||
aPromoteToJudge = new QAction(QString(), this);
|
||||
aDemoteFromJudge = new QAction(QString(), this);
|
||||
aPromoteToDeveloper = new QAction(QString(), this);
|
||||
aDemoteFromDeveloper = new QAction(QString(), this);
|
||||
aGetAdminNotes = new QAction(QString(), this);
|
||||
aInvestigateUser = new QAction(QString(), this);
|
||||
|
||||
|
|
@ -76,6 +78,8 @@ void UserContextMenu::retranslateUi()
|
|||
aDemoteFromMod->setText(tr("Dem&ote user from moderator"));
|
||||
aPromoteToJudge->setText(tr("Promote user to &judge"));
|
||||
aDemoteFromJudge->setText(tr("Demote user from judge"));
|
||||
aPromoteToDeveloper->setText(tr("Promote user to &developer"));
|
||||
aDemoteFromDeveloper->setText(tr("Demote user from de&veloper"));
|
||||
aGetAdminNotes->setText(tr("View admin notes"));
|
||||
aInvestigateUser->setText(tr("Investigate user"));
|
||||
}
|
||||
|
|
@ -268,7 +272,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const
|
|||
const Command_AdjustMod &cmd = commandContainer.admin_command(0).GetExtension(Command_AdjustMod::ext);
|
||||
|
||||
if (resp.response_code() == Response::RespOk) {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge()) {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge() || cmd.should_be_developer()) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Success"),
|
||||
tr("Successfully promoted user."));
|
||||
} else {
|
||||
|
|
@ -276,7 +280,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const
|
|||
}
|
||||
|
||||
} else {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge()) {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge() || cmd.should_be_developer()) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Failed"), tr("Failed to promote user."));
|
||||
} else {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Failed"), tr("Failed to demote user."));
|
||||
|
|
@ -437,6 +441,15 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
|
|||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aPromoteToJudge);
|
||||
}
|
||||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsDeveloper) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aDemoteFromDeveloper);
|
||||
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aPromoteToDeveloper);
|
||||
}
|
||||
}
|
||||
aDetails->setEnabled(true);
|
||||
aChat->setEnabled(anotherUser && online);
|
||||
|
|
@ -455,6 +468,10 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
|
|||
aInvestigateUser->setEnabled(anotherUser);
|
||||
aPromoteToMod->setEnabled(anotherUser);
|
||||
aDemoteFromMod->setEnabled(anotherUser);
|
||||
aPromoteToJudge->setEnabled(anotherUser);
|
||||
aDemoteFromJudge->setEnabled(anotherUser);
|
||||
aPromoteToDeveloper->setEnabled(anotherUser);
|
||||
aDemoteFromDeveloper->setEnabled(anotherUser);
|
||||
|
||||
QAction *actionClicked = menu->exec(pos);
|
||||
if (actionClicked == nullptr) {
|
||||
|
|
@ -489,6 +506,8 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
|
|||
execAdjustMod(userName, actionClicked == aPromoteToMod);
|
||||
} else if (actionClicked == aPromoteToJudge || actionClicked == aDemoteFromJudge) {
|
||||
execAdjustJudge(userName, actionClicked == aPromoteToJudge);
|
||||
} else if (actionClicked == aPromoteToDeveloper || actionClicked == aDemoteFromDeveloper) {
|
||||
execAdjustDeveloper(userName, actionClicked == aPromoteToDeveloper);
|
||||
} else if (actionClicked == aBanHistory) {
|
||||
execBanHistory(userName);
|
||||
} else if (actionClicked == aWarnUser) {
|
||||
|
|
@ -698,4 +717,14 @@ void UserContextMenu::execAdjustJudge(const QString &userName, bool shouldBeJudg
|
|||
PendingCommand *pend = client->prepareAdminCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
}
|
||||
|
||||
void UserContextMenu::execAdjustDeveloper(const QString &userName, bool shouldBeDeveloper)
|
||||
{
|
||||
Command_AdjustMod cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
cmd.set_should_be_developer(shouldBeDeveloper);
|
||||
PendingCommand *pend = client->prepareAdminCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ private:
|
|||
QAction *aBan, *aBanHistory;
|
||||
QAction *aPromoteToMod, *aDemoteFromMod;
|
||||
QAction *aPromoteToJudge, *aDemoteFromJudge;
|
||||
QAction *aPromoteToDeveloper, *aDemoteFromDeveloper;
|
||||
QAction *aWarnUser, *aWarnHistory;
|
||||
QAction *aGetAdminNotes;
|
||||
std::function<QList<GameInviteOption>()> gameInviteLinkProvider;
|
||||
|
|
@ -123,6 +124,7 @@ public:
|
|||
void execInvestigateUser(const QString &userName);
|
||||
void execAdjustMod(const QString &userName, bool shouldBeMod);
|
||||
void execAdjustJudge(const QString &userName, bool shouldBeJudge);
|
||||
void execAdjustDeveloper(const QString &userName, bool shouldBeDeveloper);
|
||||
|
||||
private:
|
||||
void execInvite(const QString &userName, const GameInviteOption &option);
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user)
|
|||
QString userLevelText;
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
userLevelText = tr("Administrator");
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
userLevelText = tr("Developer");
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
userLevelText = tr("Moderator");
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,9 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *)
|
|||
if (level.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
return QColor(245, 158, 11);
|
||||
}
|
||||
if (level.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
return QColor(185, 28, 28);
|
||||
}
|
||||
if (level.testFlag(ServerInfo_User::IsModerator)) {
|
||||
return QColor(59, 130, 246);
|
||||
}
|
||||
|
|
@ -300,6 +303,8 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *)
|
|||
} badge;
|
||||
if (level.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
badge = {"ADMIN", QColor(245, 158, 11)};
|
||||
} else if (level.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
badge = {"DEV", QColor(185, 28, 28)};
|
||||
} else if (level.testFlag(ServerInfo_User::IsModerator)) {
|
||||
badge = {"MOD", QColor(59, 130, 246)};
|
||||
} else if (level.testFlag(ServerInfo_User::IsJudge)) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ QColor UserListPainter::getAccentColor(const UserLevelFlags &userLevel, bool onl
|
|||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
accentColor = QColor(245, 158, 11);
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
accentColor = QColor(185, 28, 28);
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
accentColor = QColor(59, 130, 246);
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsJudge)) {
|
||||
|
|
@ -299,6 +301,8 @@ QList<UserListPainter::Badge> UserListPainter::buildBadges(const UserLevelFlags
|
|||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
badges << Badge{"ADMIN", QColor(245, 158, 11)};
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsDeveloper)) {
|
||||
badges << Badge{"DEV", QColor(185, 28, 28)};
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
badges << Badge{"MOD", QColor(59, 130, 246)};
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsJudge)) {
|
||||
|
|
@ -385,9 +389,9 @@ void UserListPainter::paint(QPainter *painter,
|
|||
const QString userName = QString::fromStdString(userInfo.name());
|
||||
const QString privLevel = QString::fromStdString(userInfo.privlevel());
|
||||
const QColor accentColor = getAccentColor(userLevel, online);
|
||||
const bool hasRole = userLevel.testFlag(ServerInfo_User::IsAdmin) ||
|
||||
userLevel.testFlag(ServerInfo_User::IsModerator) ||
|
||||
userLevel.testFlag(ServerInfo_User::IsJudge);
|
||||
const bool hasRole =
|
||||
userLevel.testFlag(ServerInfo_User::IsAdmin) || userLevel.testFlag(ServerInfo_User::IsDeveloper) ||
|
||||
userLevel.testFlag(ServerInfo_User::IsModerator) || userLevel.testFlag(ServerInfo_User::IsJudge);
|
||||
const QRectF cardRect = QRectF(rect).adjusted(3, 2, -3, -2);
|
||||
const int cardRight = getCardRight(option, rect);
|
||||
|
||||
|
|
|
|||
|
|
@ -234,9 +234,11 @@ bool UserListTWI::operator<(const QTreeWidgetItem &other) const
|
|||
const auto &lhsUserLevelFlags = UserLevelFlags(data(0, Qt::UserRole).toInt());
|
||||
const auto &rhsUserLevelFlags = UserLevelFlags(other.data(0, Qt::UserRole).toInt());
|
||||
|
||||
// Admins & Mods need no additional comparison checks, just to see if they're an admin or a moderator
|
||||
// Admins, Developers & Mods need no additional comparison checks, just to see if they're an admin, a developer
|
||||
// or a moderator
|
||||
static const QList<ServerInfo_User_UserLevelFlag> userLevelWithNoOtherPrefOrder = {
|
||||
ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsModerator};
|
||||
ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsDeveloper,
|
||||
ServerInfo_User_UserLevelFlag_IsModerator};
|
||||
for (const auto &userLevelEntry : userLevelWithNoOtherPrefOrder) {
|
||||
if (lhsUserLevelFlags.testFlag(userLevelEntry) &&
|
||||
lhsUserLevelFlags.testFlag(userLevelEntry) == rhsUserLevelFlags.testFlag(userLevelEntry)) {
|
||||
|
|
|
|||
122
cockatrice/src/interface/widgets/tabs/tab_developer.cpp
Normal file
122
cockatrice/src/interface/widgets/tabs/tab_developer.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* @file tab_developer.cpp
|
||||
* @ingroup ServerTabs
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#include "tab_developer.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/command_get_server_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_server_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
TabDeveloper::TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_client)
|
||||
: Tab(_tabSupervisor), client(_client)
|
||||
{
|
||||
statsTable = new QTableWidget(0, 2);
|
||||
statsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
statsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
statsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
statsTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
statsTable->horizontalHeader()->setStretchLastSection(true);
|
||||
statsTable->verticalHeader()->setVisible(false);
|
||||
|
||||
statusLabel = new QLabel;
|
||||
|
||||
refreshButton = new QPushButton;
|
||||
refreshButton->setAutoDefault(true);
|
||||
connect(refreshButton, &QPushButton::clicked, this, &TabDeveloper::refreshClicked);
|
||||
|
||||
auto *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addWidget(statusLabel, 1, Qt::AlignLeft);
|
||||
buttonLayout->addWidget(refreshButton, 0, Qt::AlignRight);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(statsTable);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
auto *central = new QWidget;
|
||||
central->setLayout(mainLayout);
|
||||
setCentralWidget(central);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void TabDeveloper::retranslateUi()
|
||||
{
|
||||
refreshButton->setText(tr("Refresh server stats"));
|
||||
statsTable->setHorizontalHeaderLabels(QString(tr("Statistic;Value")).split(";"));
|
||||
if (statsTable->rowCount() == 0) {
|
||||
statusLabel->clear();
|
||||
}
|
||||
}
|
||||
|
||||
QString TabDeveloper::formatBytes(quint64 bytes)
|
||||
{
|
||||
const quint64 kib = 1024;
|
||||
const quint64 mib = 1024 * kib;
|
||||
const quint64 gib = 1024 * mib;
|
||||
if (bytes >= gib) {
|
||||
return tr("%1 GiB").arg(QString::number(bytes / static_cast<double>(gib), 'f', 2));
|
||||
}
|
||||
if (bytes >= mib) {
|
||||
return tr("%1 MiB").arg(QString::number(bytes / static_cast<double>(mib), 'f', 2));
|
||||
}
|
||||
if (bytes >= kib) {
|
||||
return tr("%1 KiB").arg(QString::number(bytes / static_cast<double>(kib), 'f', 2));
|
||||
}
|
||||
return tr("%1 bytes").arg(bytes);
|
||||
}
|
||||
|
||||
void TabDeveloper::appendStatRow(const QString &name, const QString &value)
|
||||
{
|
||||
const int row = statsTable->rowCount();
|
||||
statsTable->insertRow(row);
|
||||
statsTable->setItem(row, 0, new QTableWidgetItem(name));
|
||||
statsTable->setItem(row, 1, new QTableWidgetItem(value));
|
||||
}
|
||||
|
||||
void TabDeveloper::refreshClicked()
|
||||
{
|
||||
Command_GetServerStats cmd;
|
||||
PendingCommand *pend = client->prepareDeveloperCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &TabDeveloper::serverStatsResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeveloper::serverStatsResponse(const Response &resp)
|
||||
{
|
||||
if (resp.response_code() != Response::RespOk) {
|
||||
statusLabel->setText(tr("No server statistics available yet."));
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_GetServerStats &response = resp.GetExtension(Response_GetServerStats::ext);
|
||||
|
||||
statsTable->setRowCount(0);
|
||||
appendStatRow(tr("Registered users online"), QString::number(response.users_count()));
|
||||
appendStatRow(tr("Moderators online"), QString::number(response.mods_count()));
|
||||
appendStatRow(tr("Games running"), QString::number(response.games_count()));
|
||||
appendStatRow(tr("Traffic sent (last tick)"), formatBytes(response.tx_bytes()));
|
||||
appendStatRow(tr("Traffic received (last tick)"), formatBytes(response.rx_bytes()));
|
||||
|
||||
const qint64 uptime = static_cast<qint64>(response.uptime_secs());
|
||||
const int days = static_cast<int>(uptime / 86400);
|
||||
const int hours = static_cast<int>((uptime % 86400) / 3600);
|
||||
const int minutes = static_cast<int>((uptime % 3600) / 60);
|
||||
appendStatRow(tr("Server uptime"), days > 0 ? tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes)
|
||||
: tr("%1h %2m").arg(hours).arg(minutes));
|
||||
|
||||
const QDateTime snapshotTime = QDateTime::fromSecsSinceEpoch(static_cast<qint64>(response.timest()));
|
||||
appendStatRow(tr("Snapshot taken"), snapshotTime.toLocalTime().toString("yyyy-MM-dd HH:mm"));
|
||||
|
||||
statsTable->resizeColumnsToContents();
|
||||
statusLabel->setText(tr("Updated %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm")));
|
||||
}
|
||||
43
cockatrice/src/interface/widgets/tabs/tab_developer.h
Normal file
43
cockatrice/src/interface/widgets/tabs/tab_developer.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @file tab_developer.h
|
||||
* @ingroup ServerTabs
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef TAB_DEVELOPER_H
|
||||
#define TAB_DEVELOPER_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
class AbstractClient;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QTableWidget;
|
||||
class Response;
|
||||
|
||||
class TabDeveloper : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
QTableWidget *statsTable;
|
||||
QPushButton *refreshButton;
|
||||
QLabel *statusLabel;
|
||||
|
||||
void appendStatRow(const QString &name, const QString &value);
|
||||
static QString formatBytes(quint64 bytes);
|
||||
|
||||
private slots:
|
||||
void refreshClicked();
|
||||
void serverStatsResponse(const Response &resp);
|
||||
|
||||
public:
|
||||
explicit TabDeveloper(TabSupervisor *_tabSupervisor, AbstractClient *_client);
|
||||
void retranslateUi() override;
|
||||
[[nodiscard]] QString getTabText() const override
|
||||
{
|
||||
return tr("Developer");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/utility/string_limits.h>
|
||||
|
||||
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
||||
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _canUseDeveloperCommands)
|
||||
: Tab(_tabSupervisor), client(_client), canUseDeveloperCommands(_canUseDeveloperCommands)
|
||||
{
|
||||
roomTable = new QTableWidget();
|
||||
roomTable->setColumnCount(6);
|
||||
|
|
@ -80,7 +81,9 @@ void TabLog::getClicked()
|
|||
if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) {
|
||||
mainRoom->setChecked(true);
|
||||
gameRoom->setChecked(true);
|
||||
privateChat->setChecked(true);
|
||||
if (!canUseDeveloperCommands) {
|
||||
privateChat->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (maximumResults->value() == 0) {
|
||||
|
|
@ -117,7 +120,15 @@ void TabLog::getClicked()
|
|||
};
|
||||
cmd.set_date_range(dateRange);
|
||||
cmd.set_maximum_results(maximumResults->value());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
|
||||
PendingCommand *pend;
|
||||
if (canUseDeveloperCommands) {
|
||||
// Developers query logs through the developer command family.
|
||||
pend = client->prepareDeveloperCommand(cmd);
|
||||
} else {
|
||||
pend = client->prepareModeratorCommand(cmd);
|
||||
}
|
||||
|
||||
connect(pend, &PendingCommand::finished, this, &TabLog::viewLogHistory_processResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
|
@ -171,6 +182,14 @@ void TabLog::createDock()
|
|||
mainRoom = new QCheckBox(tr("Main Room"));
|
||||
gameRoom = new QCheckBox(tr("Game Room"));
|
||||
privateChat = new QCheckBox(tr("Private Chat"));
|
||||
if (canUseDeveloperCommands) {
|
||||
// Developers cannot query private conversations.
|
||||
privateChat->setVisible(false);
|
||||
// The developer family ignores the IP filter server-side, so showing
|
||||
// the field would silently unfilter the result by it. Hide it.
|
||||
labelFindIPAddress->setVisible(false);
|
||||
findIPAddress->setVisible(false);
|
||||
}
|
||||
|
||||
pastDays = new QRadioButton(tr("Past X Days: "));
|
||||
today = new QRadioButton(tr("Today"));
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class TabLog : public Tab
|
|||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
bool canUseDeveloperCommands;
|
||||
QLabel *labelFindUserName, *labelFindIPAddress, *labelFindGameName, *labelFindGameID, *labelMessage, *labelMaximum,
|
||||
*labelDescription;
|
||||
LineEditUnfocusable *findUsername, *findIPAddress, *findGameName, *findGameID, *findMessage;
|
||||
|
|
@ -58,7 +59,7 @@ private slots:
|
|||
void restartLayout();
|
||||
|
||||
public:
|
||||
TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client);
|
||||
TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _canUseDeveloperCommands = false);
|
||||
~TabLog() override;
|
||||
void retranslateUi() override;
|
||||
[[nodiscard]] QString getTabText() const override
|
||||
|
|
|
|||
|
|
@ -381,6 +381,9 @@ void TabModeration::moderatorLoginsResponse(const Response &response)
|
|||
if (login.user_level() & ServerInfo_User::IsAdmin) {
|
||||
levels << tr("Admin");
|
||||
}
|
||||
if (login.user_level() & ServerInfo_User::IsDeveloper) {
|
||||
levels << tr("Developer");
|
||||
}
|
||||
if (login.user_level() & ServerInfo_User::IsModerator) {
|
||||
levels << tr("Moderator");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "tab_card_art_rules.h"
|
||||
#include "tab_deck_editor.h"
|
||||
#include "tab_deck_storage.h"
|
||||
#include "tab_developer.h"
|
||||
#include "tab_game.h"
|
||||
#include "tab_home.h"
|
||||
#include "tab_logs.h"
|
||||
|
|
@ -118,7 +119,7 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/)
|
|||
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
|
||||
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabVisualDeckStorage(nullptr),
|
||||
tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr),
|
||||
tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), isLocalGame(false)
|
||||
tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), tabDeveloper(nullptr), isLocalGame(false)
|
||||
{
|
||||
setElideMode(Qt::ElideRight);
|
||||
setMovable(true);
|
||||
|
|
@ -204,6 +205,10 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
|
|||
aTabModeration->setCheckable(true);
|
||||
connect(aTabModeration, &QAction::triggered, this, &TabSupervisor::actTabModeration);
|
||||
|
||||
aTabDeveloper = new QAction(this);
|
||||
aTabDeveloper->setCheckable(true);
|
||||
connect(aTabDeveloper, &QAction::triggered, this, &TabSupervisor::actTabDeveloper);
|
||||
|
||||
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
|
||||
&TabSupervisor::refreshShortcuts);
|
||||
refreshShortcuts();
|
||||
|
|
@ -245,6 +250,7 @@ void TabSupervisor::retranslateUi()
|
|||
aTabLog->setText(tr("Logs"));
|
||||
aTabReport->setText(tr("Report Queue"));
|
||||
aTabModeration->setText(tr("Moderation"));
|
||||
aTabDeveloper->setText(tr("Developer"));
|
||||
|
||||
// tabs
|
||||
QList<Tab *> tabs;
|
||||
|
|
@ -256,6 +262,7 @@ void TabSupervisor::retranslateUi()
|
|||
tabs.append(tabLog);
|
||||
tabs.append(tabReport);
|
||||
tabs.append(tabModeration);
|
||||
tabs.append(tabDeveloper);
|
||||
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
|
||||
while (roomIterator.hasNext()) {
|
||||
tabs.append(roomIterator.next().value());
|
||||
|
|
@ -523,6 +530,19 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
|
|||
openTabCardArtRules();
|
||||
}
|
||||
|
||||
if (userInfo->user_level() & ServerInfo_User::IsDeveloper) {
|
||||
tabsMenu->addSeparator();
|
||||
tabsMenu->addAction(aTabDeveloper);
|
||||
// Developers without moderation rights get log access through their
|
||||
// own role. Moderators already have the Logs entry from above.
|
||||
if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) {
|
||||
tabsMenu->addAction(aTabLog);
|
||||
if (SettingsCache::instance().tabs().getTabLogOpen()) {
|
||||
openTabLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
|
|
@ -535,6 +555,7 @@ void TabSupervisor::startLocal(const QList<AbstractClient *> &_clients)
|
|||
tabLog = nullptr;
|
||||
tabReport = nullptr;
|
||||
tabModeration = nullptr;
|
||||
tabDeveloper = nullptr;
|
||||
isLocalGame = true;
|
||||
userInfo = new ServerInfo_User;
|
||||
localClients = _clients;
|
||||
|
|
@ -582,6 +603,9 @@ void TabSupervisor::stop()
|
|||
if (tabModeration) {
|
||||
tabModeration->close();
|
||||
}
|
||||
if (tabDeveloper) {
|
||||
tabDeveloper->close();
|
||||
}
|
||||
}
|
||||
|
||||
QList<Tab *> tabsToDelete;
|
||||
|
|
@ -810,7 +834,13 @@ void TabSupervisor::actTabLog(bool checked)
|
|||
|
||||
void TabSupervisor::openTabLog()
|
||||
{
|
||||
tabLog = new TabLog(this, client);
|
||||
// Developers query logs through the developer command family, so tell the
|
||||
// tab which family to use. The moderator family is strictly stronger, so a
|
||||
// moderator who also holds the developer bit keeps the moderator path — the
|
||||
// developer bit only selects the (narrowed) developer family on its own.
|
||||
const bool useDeveloperCommands = (userInfo->user_level() & ServerInfo_User::IsDeveloper) &&
|
||||
!(userInfo->user_level() & ServerInfo_User::IsModerator);
|
||||
tabLog = new TabLog(this, client, useDeveloperCommands);
|
||||
myAddTab(tabLog, aTabLog);
|
||||
connect(tabLog, &QObject::destroyed, this, [this] {
|
||||
tabLog = nullptr;
|
||||
|
|
@ -872,6 +902,27 @@ void TabSupervisor::openTabModeration(const QString &userName)
|
|||
aTabModeration->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabDeveloper(bool checked)
|
||||
{
|
||||
if (checked && !tabDeveloper) {
|
||||
openTabDeveloper();
|
||||
setCurrentWidget(tabDeveloper);
|
||||
} else if (!checked && tabDeveloper) {
|
||||
tabDeveloper->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabDeveloper()
|
||||
{
|
||||
tabDeveloper = new TabDeveloper(this, client);
|
||||
myAddTab(tabDeveloper, aTabDeveloper);
|
||||
connect(tabDeveloper, &QObject::destroyed, this, [this] {
|
||||
tabDeveloper = nullptr;
|
||||
aTabDeveloper->setChecked(false);
|
||||
});
|
||||
aTabDeveloper->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::updatePingTime(int value, int max)
|
||||
{
|
||||
if (!tabServer) {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class TabReport;
|
|||
class TabModeration;
|
||||
class TabAccount;
|
||||
class TabDeckEditor;
|
||||
class TabDeveloper;
|
||||
class TabLog;
|
||||
class RoomEvent;
|
||||
class GameEventContainer;
|
||||
|
|
@ -108,6 +109,7 @@ private:
|
|||
TabLog *tabLog;
|
||||
TabReport *tabReport;
|
||||
TabModeration *tabModeration;
|
||||
TabDeveloper *tabDeveloper;
|
||||
QMap<int, TabRoom *> roomTabs;
|
||||
QMap<int, TabGame *> gameTabs;
|
||||
QList<TabGame *> replayTabs;
|
||||
|
|
@ -117,7 +119,7 @@ private:
|
|||
|
||||
QAction *aTabHome, *aTabDeckEditor, *aTabVisualDeckEditor, *aTabEdhRec, *aTabArchidekt, *aTabVisualDeckStorage,
|
||||
*aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, *aTabAdmin,
|
||||
*aTabCardArtRules, *aTabLog, *aTabReport, *aTabModeration;
|
||||
*aTabCardArtRules, *aTabLog, *aTabReport, *aTabModeration, *aTabDeveloper;
|
||||
|
||||
int myAddTab(Tab *tab, QAction *manager = nullptr);
|
||||
void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager);
|
||||
|
|
@ -207,6 +209,7 @@ private slots:
|
|||
void actTabLog(bool checked);
|
||||
void actTabReport(bool checked);
|
||||
void actTabModeration(bool checked);
|
||||
void actTabDeveloper(bool checked);
|
||||
|
||||
void openTabVisualDeckStorage();
|
||||
void openTabHome();
|
||||
|
|
@ -218,6 +221,7 @@ private slots:
|
|||
void openTabCardArtRules();
|
||||
void openTabLog();
|
||||
void openTabReport();
|
||||
void openTabDeveloper();
|
||||
|
||||
void updateCurrent(int index);
|
||||
void updatePingTime(int value, int max);
|
||||
|
|
|
|||
|
|
@ -253,3 +253,24 @@ PendingCommand *AbstractClient::prepareAdminCommand(const ::google::protobuf::Me
|
|||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareDeveloperCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
DeveloperCommand *c = cont.add_developer_command();
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ public:
|
|||
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
|
||||
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareDeveloperCommand(const ::google::protobuf::Message &cmd);
|
||||
|
||||
QMap<QString, bool> clientFeatures;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -388,6 +388,33 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons
|
|||
return finalResponseCode;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_ProtocolHandler::processDeveloperCommandContainer(const CommandContainer &cont,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
if (!userInfo) {
|
||||
return Response::RespLoginNeeded;
|
||||
}
|
||||
if (!(userInfo->user_level() & ServerInfo_User::IsDeveloper)) {
|
||||
return Response::RespLoginNeeded;
|
||||
}
|
||||
|
||||
resetIdleTimer();
|
||||
|
||||
Response::ResponseCode finalResponseCode = Response::RespOk;
|
||||
for (int i = cont.developer_command_size() - 1; i >= 0; --i) {
|
||||
Response::ResponseCode resp = Response::RespInvalidCommand;
|
||||
const DeveloperCommand &sc = cont.developer_command(i);
|
||||
const int num = getPbExtension(sc);
|
||||
logDebugMessage(getSafeDebugString(sc));
|
||||
|
||||
resp = processExtendedDeveloperCommand(num, sc, rc);
|
||||
if (resp != Response::RespOk) {
|
||||
finalResponseCode = resp;
|
||||
}
|
||||
}
|
||||
return finalResponseCode;
|
||||
}
|
||||
|
||||
void Server_ProtocolHandler::processCommandContainer(const CommandContainer &cont)
|
||||
{
|
||||
// Command processing must be disabled after prepareDestroy() has been called.
|
||||
|
|
@ -410,6 +437,8 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con
|
|||
finalResponseCode = processModeratorCommandContainer(cont, responseContainer);
|
||||
} else if (cont.admin_command_size()) {
|
||||
finalResponseCode = processAdminCommandContainer(cont, responseContainer);
|
||||
} else if (cont.developer_command_size()) {
|
||||
finalResponseCode = processDeveloperCommandContainer(cont, responseContainer);
|
||||
} else {
|
||||
finalResponseCode = Response::RespInvalidCommand;
|
||||
}
|
||||
|
|
@ -454,11 +483,12 @@ void Server_ProtocolHandler::pingClockTimeout()
|
|||
prepareDestroy();
|
||||
}
|
||||
|
||||
// PrivLevel users, Moderators, and Admins are not subject to the server idle timeout policy
|
||||
// PrivLevel users, Moderators, Admins, and Developers are not subject to the server idle timeout policy
|
||||
const bool hasPrivLevel = userInfo && QString::fromStdString(userInfo->privlevel()).toLower() != "none";
|
||||
const bool isModOrAdmin =
|
||||
userInfo && (userInfo->user_level() & (ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin));
|
||||
if (!hasPrivLevel && !isModOrAdmin) {
|
||||
const bool isStaff =
|
||||
userInfo && (userInfo->user_level() &
|
||||
(ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin | ServerInfo_User::IsDeveloper));
|
||||
if (!hasPrivLevel && !isStaff) {
|
||||
if ((server->getIdleClientTimeout() > 0) && (idleClientWarningSent)) {
|
||||
if (timeRunning - lastActionReceived > server->getIdleClientTimeout()) {
|
||||
prepareDestroy();
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class CommandContainer;
|
|||
class SessionCommand;
|
||||
class ModeratorCommand;
|
||||
class AdminCommand;
|
||||
class DeveloperCommand;
|
||||
|
||||
class Command_Ping;
|
||||
class Command_Login;
|
||||
|
|
@ -98,6 +99,12 @@ private:
|
|||
{
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
Response::ResponseCode processDeveloperCommandContainer(const CommandContainer &cont, ResponseContainer &rc);
|
||||
virtual Response::ResponseCode
|
||||
processExtendedDeveloperCommand(int /* cmdType */, const DeveloperCommand & /* cmd */, ResponseContainer & /* rc */)
|
||||
{
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
void resetIdleTimer();
|
||||
private slots:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ set(PROTO_FILES
|
|||
command_dump_zone.proto
|
||||
command_flip_card.proto
|
||||
command_game_say.proto
|
||||
command_get_server_stats.proto
|
||||
command_inc_card_counter.proto
|
||||
command_inc_counter.proto
|
||||
command_kick_from_game.proto
|
||||
|
|
@ -71,6 +72,7 @@ set(PROTO_FILES
|
|||
context_ready_start.proto
|
||||
context_set_sideboard_lock.proto
|
||||
context_undo_draw.proto
|
||||
developer_commands.proto
|
||||
event_add_to_list.proto
|
||||
event_attach_card.proto
|
||||
event_change_zone_properties.proto
|
||||
|
|
@ -141,6 +143,7 @@ set(PROTO_FILES
|
|||
response_forgotpasswordrequest.proto
|
||||
response_get_admin_notes.proto
|
||||
response_get_games_of_user.proto
|
||||
response_get_server_stats.proto
|
||||
response_get_user_info.proto
|
||||
response_join_room.proto
|
||||
response_list_users.proto
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ message Command_AdjustMod {
|
|||
required string user_name = 1;
|
||||
optional bool should_be_mod = 2;
|
||||
optional bool should_be_judge = 3;
|
||||
optional bool should_be_developer = 4;
|
||||
}
|
||||
|
||||
message Command_ResetUserPassword {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
import "developer_commands.proto";
|
||||
|
||||
message Command_GetServerStats {
|
||||
extend DeveloperCommand {
|
||||
optional Command_GetServerStats ext = 1000;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import "game_commands.proto";
|
|||
import "room_commands.proto";
|
||||
import "moderator_commands.proto";
|
||||
import "admin_commands.proto";
|
||||
import "developer_commands.proto";
|
||||
|
||||
message CommandContainer {
|
||||
optional uint64 cmd_id = 1;
|
||||
|
|
@ -16,4 +17,5 @@ message CommandContainer {
|
|||
repeated RoomCommand room_command = 102;
|
||||
repeated ModeratorCommand moderator_command = 103;
|
||||
repeated AdminCommand admin_command = 104;
|
||||
repeated DeveloperCommand developer_command = 105;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
syntax = "proto2";
|
||||
message DeveloperCommand {
|
||||
enum DeveloperCommandType {
|
||||
GET_SERVER_STATS = 1000;
|
||||
VIEWLOG_HISTORY = 1001;
|
||||
}
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
syntax = "proto2";
|
||||
import "developer_commands.proto";
|
||||
message ModeratorCommand {
|
||||
enum ModeratorCommandType {
|
||||
BAN_FROM_SERVER = 1000;
|
||||
|
|
@ -80,6 +81,9 @@ message Command_ViewLogHistory {
|
|||
extend ModeratorCommand {
|
||||
optional Command_ViewLogHistory ext = 1005;
|
||||
}
|
||||
extend DeveloperCommand {
|
||||
optional Command_ViewLogHistory dev_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
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ message Response {
|
|||
FORGOT_PASSWORD_REQUEST = 1016; // Response to password reset request
|
||||
PASSWORD_SALT = 1017; // Response containing password salt
|
||||
GET_ADMIN_NOTES = 1018; // Response with admin notes
|
||||
GET_SERVER_STATS = 1019; // Response with server status statistics
|
||||
REPLAY_LIST = 1100; // Response listing replays
|
||||
REPLAY_DOWNLOAD = 1101; // Response for replay download
|
||||
REPLAY_GET_CODE = 1102; // Response containing replay code
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
syntax = "proto2";
|
||||
import "response.proto";
|
||||
|
||||
message Response_GetServerStats {
|
||||
extend Response {
|
||||
optional Response_GetServerStats ext = 1220;
|
||||
}
|
||||
|
||||
optional uint64 users_count = 1;
|
||||
optional uint64 mods_count = 2;
|
||||
optional uint64 games_count = 3;
|
||||
|
||||
// Traffic recorded during the last status update tick
|
||||
optional uint64 tx_bytes = 4;
|
||||
optional uint64 rx_bytes = 5;
|
||||
|
||||
optional uint64 uptime_secs = 6;
|
||||
optional uint64 timest = 7; // unix timestamp of the snapshot
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ message ServerInfo_User {
|
|||
IsModerator = 4;
|
||||
IsAdmin = 8;
|
||||
IsJudge = 16;
|
||||
IsDeveloper = 32;
|
||||
};
|
||||
message PawnColorsOverride {
|
||||
optional string left_side = 1;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ INSERT INTO cockatrice_schema_version VALUES(36);
|
|||
-- users and user data tables
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||
`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,
|
||||
`name` varchar(35) NOT NULL,
|
||||
`realname` varchar(255) NOT NULL,
|
||||
|
|
|
|||
|
|
@ -641,6 +641,10 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer
|
|||
userLevel |= ServerInfo_User::IsJudge;
|
||||
}
|
||||
|
||||
if (is_admin & 8) {
|
||||
userLevel |= ServerInfo_User::IsDeveloper;
|
||||
}
|
||||
|
||||
result.set_user_level(userLevel);
|
||||
|
||||
const QString country = query->value(3).toString();
|
||||
|
|
@ -1448,7 +1452,7 @@ QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastL
|
|||
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");
|
||||
"WHERE (u.admin & 15) <> 0 ORDER BY u.name");
|
||||
|
||||
if (!execSqlQuery(query)) {
|
||||
qCWarning(DatabaseInterfaceLog) << "Failed to collect moderator login information: SQL Error";
|
||||
|
|
@ -1469,6 +1473,9 @@ QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastL
|
|||
if (isAdmin & 4) {
|
||||
userLevel |= ServerInfo_User::IsJudge;
|
||||
}
|
||||
if (isAdmin & 8) {
|
||||
userLevel |= ServerInfo_User::IsDeveloper;
|
||||
}
|
||||
loginDetails.set_user_level(userLevel);
|
||||
|
||||
if (!query->value(2).isNull()) {
|
||||
|
|
@ -1480,6 +1487,38 @@ QList<ServerInfo_ModeratorLogin> Servatrice_DatabaseInterface::getModeratorLastL
|
|||
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)
|
||||
{
|
||||
if (!checkSql()) {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,21 @@ public:
|
|||
QList<ServerInfo_UserSession> getUserSessions(const QString &userName, int limit);
|
||||
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName);
|
||||
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 addForgotPassword(const QString &user);
|
||||
bool removeForgotPassword(const QString &user) override;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include <libcockatrice/protocol/pb/command_deck_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_deck_new_dir.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_deck_upload.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_get_server_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_replay_delete_match.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_replay_download.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_replay_download_by_game_id.pb.h>
|
||||
|
|
@ -80,6 +81,7 @@
|
|||
#include <libcockatrice/protocol/pb/response_deck_upload.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_forgotpasswordrequest.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_admin_notes.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_server_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_moderator_last_logins.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_password_salt.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_register.pb.h>
|
||||
|
|
@ -284,7 +286,7 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
|||
case ModeratorCommand::REPORT_RESOLVE:
|
||||
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
|
||||
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:
|
||||
return cmdGrantReplayAccess(cmd.GetExtension(Command_GrantReplayAccess::ext), rc);
|
||||
case ModeratorCommand::REPLAY_DOWNLOAD_BY_GAME_ID:
|
||||
|
|
@ -337,6 +339,26 @@ AbstractServerSocketInterface::processExtendedAdminCommand(int cmdType, const Ad
|
|||
}
|
||||
}
|
||||
|
||||
// DEVELOPER FUNCTIONS.
|
||||
// Permission is checked by processDeveloperCommandContainer. Only stats-style
|
||||
// queries live here, never community moderation or server administration.
|
||||
Response::ResponseCode AbstractServerSocketInterface::processExtendedDeveloperCommand(int cmdType,
|
||||
const DeveloperCommand &cmd,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
switch ((DeveloperCommand::DeveloperCommandType)cmdType) {
|
||||
case DeveloperCommand::GET_SERVER_STATS:
|
||||
return cmdGetServerStats(cmd.GetExtension(Command_GetServerStats::ext), rc);
|
||||
case DeveloperCommand::VIEWLOG_HISTORY: {
|
||||
// Same query as the moderator log view, carried by the developer
|
||||
// command family, but narrows out private chats and sender IPs.
|
||||
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::dev_ext), rc, false);
|
||||
}
|
||||
default:
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc)
|
||||
{
|
||||
if (authState != PasswordRight) {
|
||||
|
|
@ -1020,12 +1042,13 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReplaySubmitCode(const
|
|||
// MODERATOR FUNCTIONS.
|
||||
// May be called by admins and moderators. Permission is checked by the calling function.
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Command_ViewLogHistory &cmd,
|
||||
ResponseContainer &rc)
|
||||
ResponseContainer &rc,
|
||||
bool allowPrivateChat)
|
||||
{
|
||||
|
||||
QList<ServerInfo_ChatMessage> messageList;
|
||||
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 gameID = nameFromStdString(cmd.game_id());
|
||||
QString message = textFromStdString(cmd.message());
|
||||
|
|
@ -1040,11 +1063,21 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
|||
if (nameFromStdString(cmd.log_location(i)).simplified() == "game") {
|
||||
gameType = true;
|
||||
}
|
||||
if (nameFromStdString(cmd.log_location(i)).simplified() == "chat") {
|
||||
if (nameFromStdString(cmd.log_location(i)).simplified() == "chat" && allowPrivateChat) {
|
||||
chatType = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For callers that must not see private conversations, never leave the
|
||||
// target-type filter empty: if the request only asked for "chat" (or for
|
||||
// nothing at all) the query below would carry no target_type restriction
|
||||
// and would return every row, private messages included. Fall back to the
|
||||
// game/room diagnostics the caller is allowed to see.
|
||||
if (!allowPrivateChat && !gameType && !roomType) {
|
||||
gameType = true;
|
||||
roomType = true;
|
||||
}
|
||||
|
||||
int dateRange = cmd.date_range();
|
||||
int maximumResults = cmd.maximum_results();
|
||||
|
||||
|
|
@ -1054,7 +1087,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com
|
|||
QListIterator<ServerInfo_ChatMessage> messageIterator(sqlInterface->getMessageLogHistory(
|
||||
userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults));
|
||||
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 {
|
||||
ServerInfo_ChatMessage chatMessage;
|
||||
|
|
@ -1643,6 +1680,34 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportUserInfo(const Co
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdGetServerStats(const Command_GetServerStats & /*cmd */,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
// Servatrice::statusUpdate() periodically snapshots server health into the
|
||||
// uptime table. Serve the freshest snapshot for this server.
|
||||
const auto snapshot = sqlInterface->getLatestUptimeSnapshot(servatrice->getServerID());
|
||||
if (!snapshot.valid) {
|
||||
// No snapshot yet (fresh server, or statusUpdate() has not ticked).
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
auto *re = new Response_GetServerStats;
|
||||
re->set_users_count(snapshot.usersCount);
|
||||
re->set_mods_count(snapshot.modsCount);
|
||||
re->set_games_count(snapshot.gamesCount);
|
||||
re->set_tx_bytes(snapshot.txBytes);
|
||||
re->set_rx_bytes(snapshot.rxBytes);
|
||||
re->set_uptime_secs(snapshot.uptimeSecs);
|
||||
re->set_timest(snapshot.timest);
|
||||
|
||||
rc.setResponseExtension(re);
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdReportStats(const Command_ReportStats & /*cmd */,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
|
|
@ -3215,7 +3280,7 @@ bool AbstractServerSocketInterface::removeAdminFlagFromUser(const QString &userN
|
|||
if (user) {
|
||||
Event_ConnectionClosed event;
|
||||
event.set_reason(Event_ConnectionClosed::DEMOTED);
|
||||
event.set_reason_str("Your moderator and/or judge status has been revoked.");
|
||||
event.set_reason_str("Your moderator, judge, and/or developer status has been revoked.");
|
||||
event.set_end_time(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
|
||||
SessionEvent *se = user->prepareSessionEvent(event);
|
||||
|
|
@ -3257,6 +3322,18 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAdjustMod(const Command
|
|||
}
|
||||
}
|
||||
|
||||
if (cmd.has_should_be_developer()) {
|
||||
if (cmd.should_be_developer()) {
|
||||
if (!addAdminFlagToUser(userName, 8)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
} else {
|
||||
if (!removeAdminFlagFromUser(userName, 8)) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <QMutex>
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocket>
|
||||
#include <libcockatrice/protocol/pb/command_get_server_stats.pb.h>
|
||||
#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>
|
||||
|
|
@ -115,7 +116,8 @@ private:
|
|||
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
|
||||
cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc, bool allowPrivateChat);
|
||||
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetWarnList(const Command_GetWarnList &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
|
||||
|
|
@ -151,6 +153,8 @@ private:
|
|||
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc) override;
|
||||
Response::ResponseCode
|
||||
processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc) override;
|
||||
Response::ResponseCode
|
||||
processExtendedDeveloperCommand(int cmdType, const DeveloperCommand &cmd, ResponseContainer &rc) override;
|
||||
|
||||
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
|
||||
|
|
@ -172,6 +176,8 @@ private:
|
|||
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
|
||||
|
||||
Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc);
|
||||
|
||||
bool addAdminFlagToUser(const QString &user, int flag);
|
||||
bool removeAdminFlagFromUser(const QString &user, int flag);
|
||||
|
||||
|
|
|
|||
|
|
@ -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_counter_test COMMAND server_counter_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 lag_monitor_test COMMAND lag_monitor_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_counter_test server_counter_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(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)
|
||||
|
|
@ -70,6 +72,7 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(server_card_counter_test gtest)
|
||||
add_dependencies(server_counter_test gtest)
|
||||
add_dependencies(server_rate_limiter_test gtest)
|
||||
add_dependencies(server_developer_role_test gtest)
|
||||
add_dependencies(warning_categories_test gtest)
|
||||
add_dependencies(lag_monitor_test gtest)
|
||||
add_dependencies(latency_tracker_test gtest)
|
||||
|
|
@ -104,6 +107,10 @@ target_link_libraries(
|
|||
target_link_libraries(
|
||||
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(
|
||||
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