mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Server/Client/Protocol] Let moderators clear banner cards (#7546)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
b4e24f958a
commit
93309e01a2
12 changed files with 366 additions and 14 deletions
|
|
@ -2077,6 +2077,108 @@ bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
|
|||
return query->numRowsAffected() > 0;
|
||||
}
|
||||
|
||||
Servatrice_DatabaseInterface::RemoveUserBannerResult
|
||||
Servatrice_DatabaseInterface::removeUserBanner(const QString &userName)
|
||||
{
|
||||
if (!checkSql()) {
|
||||
return RemoveUserBannerResult::DatabaseError;
|
||||
}
|
||||
|
||||
// The UPDATE alone cannot tell a missing user from a user without a banner,
|
||||
// so read the row first and let the moderator know which case they hit.
|
||||
QSqlQuery *lookupQuery = prepareQuery("SELECT card_art_params FROM {prefix}_users WHERE name = :user_name");
|
||||
lookupQuery->bindValue(":user_name", userName);
|
||||
if (!execSqlQuery(lookupQuery)) {
|
||||
return RemoveUserBannerResult::DatabaseError;
|
||||
}
|
||||
if (!lookupQuery->next()) {
|
||||
return RemoveUserBannerResult::NoSuchUser;
|
||||
}
|
||||
if (lookupQuery->value(0).toString().isEmpty()) {
|
||||
return RemoveUserBannerResult::NothingToRemove;
|
||||
}
|
||||
|
||||
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET card_art_params = NULL WHERE name = :user_name");
|
||||
query->bindValue(":user_name", userName);
|
||||
if (!execSqlQuery(query)) {
|
||||
return RemoveUserBannerResult::DatabaseError;
|
||||
}
|
||||
return RemoveUserBannerResult::Removed;
|
||||
}
|
||||
|
||||
// isCardArtAllowed() compares against the rule table with the database's
|
||||
// case-insensitive collation, so the sweep has to fold case the same way or it
|
||||
// leaves behind banners the server already treats as blacklisted.
|
||||
static QPair<QString, QString> bannerKey(const QString &cardName, const QString &cardProviderId)
|
||||
{
|
||||
return {cardName.toCaseFolded(), cardProviderId.toCaseFolded()};
|
||||
}
|
||||
|
||||
int Servatrice_DatabaseInterface::clearBlacklistedBanners()
|
||||
{
|
||||
if (!checkSql()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSqlQuery *ruleQuery = prepareQuery("SELECT card_name, card_provider_id FROM {prefix}_card_art_name_rules "
|
||||
"WHERE mode = 'DENY'");
|
||||
if (!execSqlQuery(ruleQuery)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSet<QPair<QString, QString>> denied;
|
||||
while (ruleQuery->next()) {
|
||||
denied.insert(bannerKey(ruleQuery->value(0).toString(), ruleQuery->value(1).toString()));
|
||||
}
|
||||
if (denied.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QSqlQuery *userQuery =
|
||||
prepareQuery("SELECT id, card_art_params FROM {prefix}_users WHERE card_art_params IS NOT NULL");
|
||||
if (!execSqlQuery(userQuery)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QList<QVariant> matchingIds;
|
||||
while (userQuery->next()) {
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(userQuery->value(1).toString().toUtf8());
|
||||
if (!doc.isObject()) {
|
||||
continue;
|
||||
}
|
||||
const QJsonObject obj = doc.object();
|
||||
if (denied.contains(bannerKey(obj.value("card_name").toString(), obj.value("card_provider_id").toString()))) {
|
||||
matchingIds.append(userQuery->value(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingIds.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Chunk the UPDATE so very large user tables do not exceed driver placeholder limits.
|
||||
int cleared = 0;
|
||||
constexpr int BATCH_SIZE = 500;
|
||||
for (int i = 0; i < matchingIds.size(); i += BATCH_SIZE) {
|
||||
const QList<QVariant> batch = matchingIds.mid(i, BATCH_SIZE);
|
||||
QStringList placeholders;
|
||||
placeholders.reserve(batch.size());
|
||||
for (int j = 0; j < batch.size(); ++j) {
|
||||
placeholders.append(QString(":id%1").arg(j));
|
||||
}
|
||||
QSqlQuery *clearQuery = prepareQuery(
|
||||
QString("UPDATE {prefix}_users SET card_art_params = NULL WHERE id IN (%1)").arg(placeholders.join(",")));
|
||||
for (int j = 0; j < batch.size(); ++j) {
|
||||
clearQuery->bindValue(placeholders.at(j), batch.at(j));
|
||||
}
|
||||
if (!execSqlQuery(clearQuery)) {
|
||||
return -1;
|
||||
}
|
||||
cleared += clearQuery->numRowsAffected();
|
||||
}
|
||||
return cleared;
|
||||
}
|
||||
|
||||
QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user,
|
||||
const QString &ipaddress,
|
||||
const QString &gamename,
|
||||
|
|
|
|||
|
|
@ -231,6 +231,18 @@ public:
|
|||
};
|
||||
UptimeSnapshot getLatestUptimeSnapshot(int serverId);
|
||||
bool removeUserAvatar(const QString &userName);
|
||||
|
||||
// Outcome of removeUserBanner(), so the caller can tell a missing user apart
|
||||
// from a user who simply has no banner card set.
|
||||
enum class RemoveUserBannerResult
|
||||
{
|
||||
Removed,
|
||||
NothingToRemove,
|
||||
NoSuchUser,
|
||||
DatabaseError,
|
||||
};
|
||||
RemoveUserBannerResult removeUserBanner(const QString &userName);
|
||||
int clearBlacklistedBanners();
|
||||
bool isCardArtAllowed(const QString &cardName, const QString &cardProviderId) override;
|
||||
bool addForgotPassword(const QString &user);
|
||||
bool removeForgotPassword(const QString &user) override;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_ban_history.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_card_art_rule_entry.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_clear_blacklisted_banners.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_deck_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_deck_share_create.pb.h>
|
||||
|
|
@ -140,6 +141,7 @@
|
|||
#include <libcockatrice/protocol/pb/response_password_salt.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_register.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_remove_user_avatar.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_remove_user_banner.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_replay_download.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_replay_download_by_game_id.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_replay_get_code.pb.h>
|
||||
|
|
@ -484,6 +486,10 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
|||
return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc);
|
||||
case ModeratorCommand::REMOVE_USER_AVATAR:
|
||||
return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc);
|
||||
case ModeratorCommand::REMOVE_USER_BANNER:
|
||||
return cmdRemoveUserBanner(cmd.GetExtension(Command_RemoveUserBanner::ext), rc);
|
||||
case ModeratorCommand::CLEAR_BLACKLISTED_BANNERS:
|
||||
return cmdClearBlacklistedBanners(cmd.GetExtension(Command_ClearBlacklistedBanners::ext), rc);
|
||||
default:
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
|
@ -2691,6 +2697,67 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserAvatar(const
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
const QString userName = nameFromStdString(cmd.user_name()).simplified();
|
||||
if (userName.isEmpty()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
bool hadBanner = true;
|
||||
switch (sqlInterface->removeUserBanner(userName)) {
|
||||
case Servatrice_DatabaseInterface::RemoveUserBannerResult::Removed:
|
||||
break;
|
||||
case Servatrice_DatabaseInterface::RemoveUserBannerResult::NothingToRemove:
|
||||
hadBanner = false;
|
||||
break;
|
||||
case Servatrice_DatabaseInterface::RemoveUserBannerResult::NoSuchUser:
|
||||
return Response::RespNameNotFound;
|
||||
case Servatrice_DatabaseInterface::RemoveUserBannerResult::DatabaseError:
|
||||
default:
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
if (hadBanner) {
|
||||
sqlInterface->addAuditRecord(userName, this->getAddress(), QString::fromStdString(userInfo->clientid()),
|
||||
"REMOVE_USER_BANNER", "Moderator removed user banner card", true);
|
||||
}
|
||||
|
||||
Response_RemoveUserBanner *re = new Response_RemoveUserBanner;
|
||||
re->set_user_name(userName.toStdString());
|
||||
re->set_had_banner(hadBanner);
|
||||
rc.setResponseExtension(re);
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
AbstractServerSocketInterface::cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners & /* cmd */,
|
||||
ResponseContainer &rc)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
const int cleared = sqlInterface->clearBlacklistedBanners();
|
||||
if (cleared < 0) {
|
||||
return Response::RespInternalError;
|
||||
}
|
||||
|
||||
sqlInterface->addAuditRecord(QString::fromStdString(userInfo->name()), this->getAddress(),
|
||||
QString::fromStdString(userInfo->clientid()), "CLEAR_BLACKLISTED_BANNERS",
|
||||
QString("Moderator cleared %1 blacklisted banner(s)").arg(cleared), true);
|
||||
|
||||
Response_ClearBlacklistedBanners *re = new Response_ClearBlacklistedBanners;
|
||||
re->set_cleared_count(static_cast<unsigned int>(cleared));
|
||||
rc.setResponseExtension(re);
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
void AbstractServerSocketInterface::sendPendingReportNotifications(ResponseContainer &rc)
|
||||
{
|
||||
if (!sqlInterface->checkSql()) {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class Command_AccountPassword;
|
|||
class Command_Activate;
|
||||
class Command_AddCardArtRule;
|
||||
class Command_AdjustMod;
|
||||
class Command_ClearBlacklistedBanners;
|
||||
class Command_ForceActivateUser;
|
||||
class Command_ForgotPasswordChallenge;
|
||||
class Command_ForgotPasswordRequest;
|
||||
|
|
@ -90,6 +91,7 @@ class Command_ListCardArtRules;
|
|||
class Command_Register;
|
||||
class Command_RemoveCardArtRule;
|
||||
class Command_RemoveUserAvatar;
|
||||
class Command_RemoveUserBanner;
|
||||
class Command_Report;
|
||||
class Command_ReportAddComment;
|
||||
class Command_ReportAssign;
|
||||
|
|
@ -236,6 +238,9 @@ private:
|
|||
Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd, ResponseContainer &rc);
|
||||
Response::ResponseCode cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners &cmd,
|
||||
ResponseContainer &rc);
|
||||
|
||||
Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue