[Report] Add report reopen action for closed reports (#7442)

- New Command_ReportReopen moderator command (extension 1206) with server
  handler that reopens resolved/dismissed reports, resets resolution state
  and notified, and records the reopening note in the audit log
- Reopen... button in the report queue action bar, gated to resolved and
  dismissed reports, with optional note prompt and tooltip
- Success confirmations for Assign, Resolve/Dismiss and Reopen now survive
  the list refresh via a pending status message instead of being clobbered
- Dispatch/auth coverage in server_moderator_role_test

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-10-11 15:28:45 +02:00 • committed by GitHub
parent 315cbae345
commit 4a010bd17f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 280 additions and 2 deletions

View file

@ -110,6 +110,7 @@
#include <libcockatrice/protocol/pb/command_report_details.pb.h>
#include <libcockatrice/protocol/pb/command_report_list.pb.h>
#include <libcockatrice/protocol/pb/command_report_my_list.pb.h>
#include <libcockatrice/protocol/pb/command_report_reopen.pb.h>
#include <libcockatrice/protocol/pb/command_report_resolve.pb.h>
#include <libcockatrice/protocol/pb/command_report_stats.pb.h>
#include <libcockatrice/protocol/pb/command_report_user_info.pb.h>
@ -434,6 +435,8 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
return cmdReportAssign(cmd.GetExtension(Command_ReportAssign::ext), rc);
case ModeratorCommand::REPORT_RESOLVE:
return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc);
case ModeratorCommand::REPORT_REOPEN:
return cmdReportReopen(cmd.GetExtension(Command_ReportReopen::ext), rc);
case ModeratorCommand::VIEWLOG_HISTORY:
return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true);
case ModeratorCommand::GRANT_REPLAY_ACCESS:
@ -2791,6 +2794,42 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm
return Response::RespOk;
}
Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd,
ResponseContainer & /*rc*/)
{
if (!sqlInterface->checkSql()) {
return Response::RespInternalError;
}
int reportId = cmd.report_id();
QString note = textFromStdString(cmd.reopen_note());
QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports "
"SET status = 'open', resolution_note = NULL, "
"resolution_time = NULL, resolved_by = NULL, assigned_to = NULL, "
"notified = 0 "
"WHERE id = :id AND status IN ('resolved', 'dismissed')");
query->bindValue(":id", reportId);
if (!sqlInterface->execSqlQuery(query)) {
return Response::RespInternalError;
}
if (query->numRowsAffected() == 0) {
return Response::RespInvalidData;
}
QString auditDetails = QString("Report #%1 reopened").arg(reportId);
if (!note.isEmpty()) {
auditDetails += QString(". Reason: %1").arg(note.left(200));
}
sqlInterface->addAuditRecord(QString::number(reportId), this->getAddress(),
QString::fromStdString(userInfo->clientid()), "REPORT_REOPENED", auditDetails, true);
return Response::RespOk;
}
Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Command_ReportDetails &cmd,
ResponseContainer &rc)
{