mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Alts] Server-side search controls, IP counts, adjustable window
Plumbs the match-type chips through to the server: include_email/include_clientid/include_ip skip disabled identifier types in both matching and frontier expansion, so excluded types (e.g. throwaway domains) never enter the graph at any depth. Session lookback becomes adjustable (session_months, clamped 1-24, default 6). Each alt row carries ip_count, the distinct recent session IP count shown in a new IPs column. All proto additions are optional with old defaults, so old clients/servers interoperate.
This commit is contained in:
parent
c836d8b10d
commit
a82d8f3021
7 changed files with 142 additions and 45 deletions
|
|
@ -42,14 +42,15 @@ constexpr int COL_ALTS_USER = 0;
|
||||||
constexpr int COL_ALTS_VIA = 1;
|
constexpr int COL_ALTS_VIA = 1;
|
||||||
constexpr int COL_ALTS_DEPTH = 2;
|
constexpr int COL_ALTS_DEPTH = 2;
|
||||||
constexpr int COL_ALTS_SHARED = 3;
|
constexpr int COL_ALTS_SHARED = 3;
|
||||||
constexpr int COL_ALTS_EMAIL = 4;
|
constexpr int COL_ALTS_IPS = 4;
|
||||||
constexpr int COL_ALTS_CLIENTID = 5;
|
constexpr int COL_ALTS_EMAIL = 5;
|
||||||
constexpr int COL_ALTS_REGISTERED = 6;
|
constexpr int COL_ALTS_CLIENTID = 6;
|
||||||
constexpr int COL_ALTS_LAST_LOGIN = 7;
|
constexpr int COL_ALTS_REGISTERED = 7;
|
||||||
constexpr int COL_ALTS_WARNS = 8;
|
constexpr int COL_ALTS_LAST_LOGIN = 8;
|
||||||
constexpr int COL_ALTS_BANS = 9;
|
constexpr int COL_ALTS_WARNS = 9;
|
||||||
constexpr int COL_ALTS_ACTIVE = 10;
|
constexpr int COL_ALTS_BANS = 10;
|
||||||
constexpr int COL_ALTS_COUNT = 11;
|
constexpr int COL_ALTS_ACTIVE = 11;
|
||||||
|
constexpr int COL_ALTS_COUNT = 12;
|
||||||
|
|
||||||
constexpr int COL_WARNS_TIME = 0;
|
constexpr int COL_WARNS_TIME = 0;
|
||||||
constexpr int COL_WARNS_MODERATOR = 1;
|
constexpr int COL_WARNS_MODERATOR = 1;
|
||||||
|
|
@ -94,6 +95,7 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
||||||
depthSpin->setMinimum(1);
|
depthSpin->setMinimum(1);
|
||||||
depthSpin->setMaximum(MAX_ALT_DEPTH);
|
depthSpin->setMaximum(MAX_ALT_DEPTH);
|
||||||
depthSpin->setValue(1);
|
depthSpin->setValue(1);
|
||||||
|
connect(depthSpin, &QSpinBox::valueChanged, this, &TabAlts::onSearchFilterChanged);
|
||||||
|
|
||||||
investigateButton = new QPushButton;
|
investigateButton = new QPushButton;
|
||||||
connect(investigateButton, &QPushButton::clicked, this, &TabAlts::investigateUser);
|
connect(investigateButton, &QPushButton::clicked, this, &TabAlts::investigateUser);
|
||||||
|
|
@ -126,7 +128,7 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
||||||
auto *chip = new QPushButton(text);
|
auto *chip = new QPushButton(text);
|
||||||
chip->setCheckable(true);
|
chip->setCheckable(true);
|
||||||
chip->setChecked(true);
|
chip->setChecked(true);
|
||||||
connect(chip, &QPushButton::toggled, this, &TabAlts::onFilterChanged);
|
connect(chip, &QPushButton::toggled, this, &TabAlts::onSearchFilterChanged);
|
||||||
return chip;
|
return chip;
|
||||||
};
|
};
|
||||||
emailChip = makeChip(QString());
|
emailChip = makeChip(QString());
|
||||||
|
|
@ -137,7 +139,13 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
||||||
vpnSpin->setMinimum(1);
|
vpnSpin->setMinimum(1);
|
||||||
vpnSpin->setMaximum(100000);
|
vpnSpin->setMaximum(100000);
|
||||||
vpnSpin->setValue(DEFAULT_VPN_THRESHOLD);
|
vpnSpin->setValue(DEFAULT_VPN_THRESHOLD);
|
||||||
connect(vpnSpin, &QSpinBox::valueChanged, this, &TabAlts::onFilterChanged);
|
connect(vpnSpin, &QSpinBox::valueChanged, this, &TabAlts::onDisplayFilterChanged);
|
||||||
|
windowLabel = new QLabel;
|
||||||
|
monthsSpin = new QSpinBox;
|
||||||
|
monthsSpin->setMinimum(1);
|
||||||
|
monthsSpin->setMaximum(24);
|
||||||
|
monthsSpin->setValue(6);
|
||||||
|
connect(monthsSpin, &QSpinBox::valueChanged, this, &TabAlts::onSearchFilterChanged);
|
||||||
|
|
||||||
auto *filterBar = new QHBoxLayout;
|
auto *filterBar = new QHBoxLayout;
|
||||||
filterBar->addWidget(emailChip);
|
filterBar->addWidget(emailChip);
|
||||||
|
|
@ -145,6 +153,8 @@ TabAlts::TabAlts(TabSupervisor *_tabSupervisor, AbstractClient *_client, const Q
|
||||||
filterBar->addWidget(ipChip);
|
filterBar->addWidget(ipChip);
|
||||||
filterBar->addWidget(vpnLabel);
|
filterBar->addWidget(vpnLabel);
|
||||||
filterBar->addWidget(vpnSpin);
|
filterBar->addWidget(vpnSpin);
|
||||||
|
filterBar->addWidget(windowLabel);
|
||||||
|
filterBar->addWidget(monthsSpin);
|
||||||
filterBar->addStretch();
|
filterBar->addStretch();
|
||||||
|
|
||||||
altsTable = new QTableWidget(0, COL_ALTS_COUNT);
|
altsTable = new QTableWidget(0, COL_ALTS_COUNT);
|
||||||
|
|
@ -235,8 +245,11 @@ void TabAlts::retranslateUi()
|
||||||
ipChip->setToolTip(tr("Show or hide IP address matches"));
|
ipChip->setToolTip(tr("Show or hide IP address matches"));
|
||||||
vpnLabel->setText(tr("Hide IPs shared by more than"));
|
vpnLabel->setText(tr("Hide IPs shared by more than"));
|
||||||
vpnSpin->setSuffix(tr(" users"));
|
vpnSpin->setSuffix(tr(" users"));
|
||||||
|
windowLabel->setText(tr("Session window"));
|
||||||
|
monthsSpin->setSuffix(tr(" months"));
|
||||||
|
monthsSpin->setToolTip(tr("How far back session IPs and client IDs are considered."));
|
||||||
|
|
||||||
altsTable->setHorizontalHeaderLabels({tr("User"), tr("Via"), tr("Depth"), tr("Shared"), tr("eMail"),
|
altsTable->setHorizontalHeaderLabels({tr("User"), tr("Via"), tr("Depth"), tr("Shared"), tr("IPs"), tr("eMail"),
|
||||||
tr("Client ID"), tr("Registered"), tr("Last login"), tr("Warns"), tr("Bans"),
|
tr("Client ID"), tr("Registered"), tr("Last login"), tr("Warns"), tr("Bans"),
|
||||||
tr("Active")});
|
tr("Active")});
|
||||||
altsTable->setToolTip(tr("Double-click or press Enter on a row to investigate that account"));
|
altsTable->setToolTip(tr("Double-click or press Enter on a row to investigate that account"));
|
||||||
|
|
@ -355,6 +368,10 @@ void TabAlts::requestAlts(const QString &userName, int depth)
|
||||||
Command_GetUserAlts cmd;
|
Command_GetUserAlts cmd;
|
||||||
cmd.set_user_name(userName.toStdString());
|
cmd.set_user_name(userName.toStdString());
|
||||||
cmd.set_depth(static_cast<google::protobuf::uint32>(depth));
|
cmd.set_depth(static_cast<google::protobuf::uint32>(depth));
|
||||||
|
cmd.set_include_email(emailChip->isChecked());
|
||||||
|
cmd.set_include_clientid(clientIdChip->isChecked());
|
||||||
|
cmd.set_include_ip(ipChip->isChecked());
|
||||||
|
cmd.set_session_months(static_cast<google::protobuf::uint32>(monthsSpin->value()));
|
||||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||||
connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
|
||||||
if (userName == currentUser) {
|
if (userName == currentUser) {
|
||||||
|
|
@ -456,6 +473,9 @@ void TabAlts::renderTable()
|
||||||
altsTable->setItem(
|
altsTable->setItem(
|
||||||
row, COL_ALTS_SHARED,
|
row, COL_ALTS_SHARED,
|
||||||
new QTableWidgetItem(alt.has_shared_count() ? QString::number(alt.shared_count()) : QStringLiteral("-")));
|
new QTableWidgetItem(alt.has_shared_count() ? QString::number(alt.shared_count()) : QStringLiteral("-")));
|
||||||
|
altsTable->setItem(
|
||||||
|
row, COL_ALTS_IPS,
|
||||||
|
new QTableWidgetItem(alt.has_ip_count() ? QString::number(alt.ip_count()) : QStringLiteral("-")));
|
||||||
altsTable->setItem(row, COL_ALTS_EMAIL, new QTableWidgetItem(QString::fromStdString(alt.email())));
|
altsTable->setItem(row, COL_ALTS_EMAIL, new QTableWidgetItem(QString::fromStdString(alt.email())));
|
||||||
altsTable->setItem(row, COL_ALTS_CLIENTID, new QTableWidgetItem(QString::fromStdString(alt.clientid())));
|
altsTable->setItem(row, COL_ALTS_CLIENTID, new QTableWidgetItem(QString::fromStdString(alt.clientid())));
|
||||||
altsTable->setItem(row, COL_ALTS_REGISTERED, new QTableWidgetItem(formatEpoch(alt.registration_time())));
|
altsTable->setItem(row, COL_ALTS_REGISTERED, new QTableWidgetItem(formatEpoch(alt.registration_time())));
|
||||||
|
|
@ -513,7 +533,15 @@ void TabAlts::renderTable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabAlts::onFilterChanged()
|
void TabAlts::onSearchFilterChanged()
|
||||||
|
{
|
||||||
|
if (currentUser.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
investigateUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabAlts::onDisplayFilterChanged()
|
||||||
{
|
{
|
||||||
if (lastAlts.isEmpty()) {
|
if (lastAlts.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ private slots:
|
||||||
void goBack();
|
void goBack();
|
||||||
void investigateSelected();
|
void investigateSelected();
|
||||||
void altsResponse(const Response &response);
|
void altsResponse(const Response &response);
|
||||||
void onFilterChanged();
|
void onSearchFilterChanged();
|
||||||
|
void onDisplayFilterChanged();
|
||||||
void onAltSelected();
|
void onAltSelected();
|
||||||
void userDetailsResponse(const Response &response);
|
void userDetailsResponse(const Response &response);
|
||||||
void warnHistoryResponse(const Response &response);
|
void warnHistoryResponse(const Response &response);
|
||||||
|
|
@ -82,6 +83,8 @@ private:
|
||||||
QPushButton *ipChip;
|
QPushButton *ipChip;
|
||||||
QLabel *vpnLabel;
|
QLabel *vpnLabel;
|
||||||
QSpinBox *vpnSpin;
|
QSpinBox *vpnSpin;
|
||||||
|
QLabel *windowLabel;
|
||||||
|
QSpinBox *monthsSpin;
|
||||||
|
|
||||||
QLabel *detailNameValue;
|
QLabel *detailNameValue;
|
||||||
QLabel *detailCountsValue;
|
QLabel *detailCountsValue;
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,10 @@ message Command_GetUserAlts {
|
||||||
}
|
}
|
||||||
optional string user_name = 1;
|
optional string user_name = 1;
|
||||||
optional uint32 depth = 2 [default = 1]; // BFS depth: 1 = direct matches only, up to 3 for transitive alts-of-alts
|
optional uint32 depth = 2 [default = 1]; // BFS depth: 1 = direct matches only, up to 3 for transitive alts-of-alts
|
||||||
|
optional bool include_email = 3 [default = true]; // match and expand over registration email
|
||||||
|
optional bool include_clientid = 4 [default = true]; // match and expand over current and historic client ids
|
||||||
|
optional bool include_ip = 5 [default = true]; // match and expand over recent session IPs
|
||||||
|
optional uint32 session_months = 6 [default = 6]; // session lookback window in months, 1-24
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_GetModeratorLastLogins {
|
message Command_GetModeratorLastLogins {
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,5 @@ message ServerInfo_UserAlt {
|
||||||
optional uint32 depth = 10; // BFS distance from investigated account; 1 = direct, 2+ = alt-of-alt
|
optional uint32 depth = 10; // BFS distance from investigated account; 1 = direct, 2+ = alt-of-alt
|
||||||
optional uint32 shared_count =
|
optional uint32 shared_count =
|
||||||
11; // distinct users sharing the matched identifier; high values suggest VPN/shared network
|
11; // distinct users sharing the matched identifier; high values suggest VPN/shared network
|
||||||
|
optional uint32 ip_count = 12; // distinct recent session IPs of this account in the search window
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1987,6 +1987,8 @@ constexpr int MAX_USER_ALTS_PER_LEVEL = 200;
|
||||||
// Identifier-heavy frontiers, e.g. VPN users with hundreds of recent IPs, are
|
// Identifier-heavy frontiers, e.g. VPN users with hundreds of recent IPs, are
|
||||||
// truncated: this tool is deliberately a first-pass sweep, not an audit.
|
// truncated: this tool is deliberately a first-pass sweep, not an audit.
|
||||||
constexpr int MAX_USER_ALT_IDENTIFIERS = 200;
|
constexpr int MAX_USER_ALT_IDENTIFIERS = 200;
|
||||||
|
constexpr int MIN_USER_ALT_MONTHS = 1;
|
||||||
|
constexpr int MAX_USER_ALT_MONTHS = 24;
|
||||||
|
|
||||||
QString altInClause(const QString &prefix, const QList<QString> &values)
|
QString altInClause(const QString &prefix, const QList<QString> &values)
|
||||||
{
|
{
|
||||||
|
|
@ -2037,7 +2039,12 @@ QList<QString> cappedIdentifiers(const QList<QString> &values)
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QString &userName, int depth)
|
QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QString &userName,
|
||||||
|
int depth,
|
||||||
|
bool useEmail,
|
||||||
|
bool useClientId,
|
||||||
|
bool useIp,
|
||||||
|
int sessionMonths)
|
||||||
{
|
{
|
||||||
QList<ServerInfo_UserAlt> results;
|
QList<ServerInfo_UserAlt> results;
|
||||||
|
|
||||||
|
|
@ -2050,6 +2057,16 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
} else if (depth > MAX_USER_ALT_DEPTH) {
|
} else if (depth > MAX_USER_ALT_DEPTH) {
|
||||||
depth = MAX_USER_ALT_DEPTH;
|
depth = MAX_USER_ALT_DEPTH;
|
||||||
}
|
}
|
||||||
|
if (sessionMonths < MIN_USER_ALT_MONTHS) {
|
||||||
|
sessionMonths = MIN_USER_ALT_MONTHS;
|
||||||
|
} else if (sessionMonths > MAX_USER_ALT_MONTHS) {
|
||||||
|
sessionMonths = MAX_USER_ALT_MONTHS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolated, not bound: placeholders are not allowed in INTERVAL
|
||||||
|
// expressions, and the clamped integer cannot inject SQL.
|
||||||
|
const QString sessionWindow = QStringLiteral(" AND start_time >= DATE_SUB(NOW(), INTERVAL ") +
|
||||||
|
QString::number(sessionMonths) + QStringLiteral(" MONTH)");
|
||||||
|
|
||||||
// Seed must exist; its row anchors the result at depth 0.
|
// Seed must exist; its row anchors the result at depth 0.
|
||||||
QSqlQuery *seedQuery = prepareQuery("SELECT u.name, u.email, u.clientid, UNIX_TIMESTAMP(u.registrationDate), "
|
QSqlQuery *seedQuery = prepareQuery("SELECT u.name, u.email, u.clientid, UNIX_TIMESTAMP(u.registrationDate), "
|
||||||
|
|
@ -2122,32 +2139,46 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
QSet<QString> clientIdKeys;
|
QSet<QString> clientIdKeys;
|
||||||
QSet<QString> ipKeys;
|
QSet<QString> ipKeys;
|
||||||
|
|
||||||
QSqlQuery frontierQuery(sqlDatabase);
|
if (useEmail || useClientId) {
|
||||||
prepareDynamicQuery(frontierQuery, QStringLiteral("SELECT email, clientid FROM {prefix}_users WHERE name IN ") +
|
QSqlQuery frontierQuery(sqlDatabase);
|
||||||
frontierClause);
|
prepareDynamicQuery(frontierQuery,
|
||||||
altBindValues(&frontierQuery, QStringLiteral(":alt_name"), frontier);
|
QStringLiteral("SELECT email, clientid FROM {prefix}_users WHERE name IN ") +
|
||||||
if (execSqlQuery(&frontierQuery)) {
|
frontierClause);
|
||||||
while (frontierQuery.next()) {
|
altBindValues(&frontierQuery, QStringLiteral(":alt_name"), frontier);
|
||||||
// Email comparison is case-insensitive in SQL, so normalise the
|
if (execSqlQuery(&frontierQuery)) {
|
||||||
// keys too or the membership tests below miss real matches.
|
while (frontierQuery.next()) {
|
||||||
appendIdentifier(emails, emailKeys, frontierQuery.value(0).toString().toLower());
|
// Email comparison is case-insensitive in SQL, so normalise the
|
||||||
appendIdentifier(clientIds, clientIdKeys, frontierQuery.value(1).toString());
|
// keys too or the membership tests below miss real matches.
|
||||||
|
if (useEmail) {
|
||||||
|
appendIdentifier(emails, emailKeys, frontierQuery.value(0).toString().toLower());
|
||||||
|
}
|
||||||
|
if (useClientId) {
|
||||||
|
appendIdentifier(clientIds, clientIdKeys, frontierQuery.value(1).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recent session identifiers: IPs plus historic client ids, most recent
|
// Recent session identifiers: IPs plus historic client ids, most recent
|
||||||
// first so a capped list keeps what the account actually used lately.
|
// first so a capped list keeps what the account actually used lately.
|
||||||
QSqlQuery sessionQuery(sqlDatabase);
|
if (useIp || useClientId) {
|
||||||
prepareDynamicQuery(sessionQuery, QStringLiteral("SELECT ip_address, clientid, MAX(start_time) AS last_seen "
|
QSqlQuery sessionQuery(sqlDatabase);
|
||||||
"FROM {prefix}_sessions WHERE user_name IN ") +
|
prepareDynamicQuery(sessionQuery,
|
||||||
frontierClause +
|
QStringLiteral("SELECT ip_address, clientid, MAX(start_time) AS last_seen "
|
||||||
QStringLiteral(" AND start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH) "
|
"FROM {prefix}_sessions WHERE user_name IN ") +
|
||||||
"GROUP BY ip_address, clientid ORDER BY last_seen DESC"));
|
frontierClause + sessionWindow +
|
||||||
altBindValues(&sessionQuery, QStringLiteral(":alt_name"), frontier);
|
QStringLiteral(" GROUP BY ip_address, clientid "
|
||||||
if (execSqlQuery(&sessionQuery)) {
|
"ORDER BY last_seen DESC"));
|
||||||
while (sessionQuery.next()) {
|
altBindValues(&sessionQuery, QStringLiteral(":alt_name"), frontier);
|
||||||
appendIdentifier(ips, ipKeys, sessionQuery.value(0).toString());
|
if (execSqlQuery(&sessionQuery)) {
|
||||||
appendIdentifier(clientIds, clientIdKeys, sessionQuery.value(1).toString());
|
while (sessionQuery.next()) {
|
||||||
|
if (useIp) {
|
||||||
|
appendIdentifier(ips, ipKeys, sessionQuery.value(0).toString());
|
||||||
|
}
|
||||||
|
if (useClientId) {
|
||||||
|
appendIdentifier(clientIds, clientIdKeys, sessionQuery.value(1).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2179,14 +2210,13 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
if (!ipList.isEmpty()) {
|
if (!ipList.isEmpty()) {
|
||||||
conditions << QStringLiteral("u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s WHERE "
|
conditions << QStringLiteral("u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s WHERE "
|
||||||
"s.ip_address IN ") +
|
"s.ip_address IN ") +
|
||||||
altInClause(QStringLiteral(":alt_ip"), ipList) +
|
altInClause(QStringLiteral(":alt_ip"), ipList) + sessionWindow + QStringLiteral(")");
|
||||||
QStringLiteral(" AND s.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH))");
|
|
||||||
}
|
}
|
||||||
if (!clientIdList.isEmpty()) {
|
if (!clientIdList.isEmpty()) {
|
||||||
conditions << QStringLiteral("u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s WHERE "
|
conditions << QStringLiteral("u.name IN (SELECT DISTINCT s.user_name FROM {prefix}_sessions s WHERE "
|
||||||
"s.clientid IN ") +
|
"s.clientid IN ") +
|
||||||
altInClause(QStringLiteral(":alt_hcid"), clientIdList) +
|
altInClause(QStringLiteral(":alt_hcid"), clientIdList) + sessionWindow +
|
||||||
QStringLiteral(" AND s.start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH))");
|
QStringLiteral(")");
|
||||||
}
|
}
|
||||||
candidateString += conditions.join(QStringLiteral(" OR "));
|
candidateString += conditions.join(QStringLiteral(" OR "));
|
||||||
// Already-visited accounts match their own identifiers at every level and
|
// Already-visited accounts match their own identifiers at every level and
|
||||||
|
|
@ -2251,7 +2281,7 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
altInClause(QStringLiteral(":alt_ecid"), clientIdList);
|
altInClause(QStringLiteral(":alt_ecid"), clientIdList);
|
||||||
}
|
}
|
||||||
evidenceString += evidenceConditions.join(QStringLiteral(" OR "));
|
evidenceString += evidenceConditions.join(QStringLiteral(" OR "));
|
||||||
evidenceString += QStringLiteral(") AND start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH)");
|
evidenceString += QStringLiteral(")") + sessionWindow;
|
||||||
|
|
||||||
QSqlQuery evidenceQuery(sqlDatabase);
|
QSqlQuery evidenceQuery(sqlDatabase);
|
||||||
prepareDynamicQuery(evidenceQuery, evidenceString);
|
prepareDynamicQuery(evidenceQuery, evidenceString);
|
||||||
|
|
@ -2346,9 +2376,8 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
prepareDynamicQuery(countQuery,
|
prepareDynamicQuery(countQuery,
|
||||||
QStringLiteral("SELECT ip_address, COUNT(DISTINCT user_name) FROM {prefix}_sessions "
|
QStringLiteral("SELECT ip_address, COUNT(DISTINCT user_name) FROM {prefix}_sessions "
|
||||||
"WHERE ip_address IN ") +
|
"WHERE ip_address IN ") +
|
||||||
altInClause(QStringLiteral(":alt_ci"), neededIpList) +
|
altInClause(QStringLiteral(":alt_ci"), neededIpList) + sessionWindow +
|
||||||
QStringLiteral(" AND start_time >= DATE_SUB(NOW(), INTERVAL 6 MONTH) "
|
QStringLiteral(" GROUP BY ip_address"));
|
||||||
"GROUP BY ip_address"));
|
|
||||||
altBindValues(&countQuery, QStringLiteral(":alt_ci"), neededIpList);
|
altBindValues(&countQuery, QStringLiteral(":alt_ci"), neededIpList);
|
||||||
if (execSqlQuery(&countQuery)) {
|
if (execSqlQuery(&countQuery)) {
|
||||||
while (countQuery.next()) {
|
while (countQuery.next()) {
|
||||||
|
|
@ -2385,6 +2414,31 @@ QList<ServerInfo_UserAlt> Servatrice_DatabaseInterface::getUserAlts(const QStrin
|
||||||
frontier = nextFrontier;
|
frontier = nextFrontier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Distinct recent IP counts per account: cheap group signal without
|
||||||
|
// shipping full address histories to the client.
|
||||||
|
if (!results.isEmpty()) {
|
||||||
|
QList<QString> resultNames;
|
||||||
|
resultNames.reserve(results.size());
|
||||||
|
for (const ServerInfo_UserAlt &alt : results) {
|
||||||
|
resultNames << QString::fromStdString(alt.user_name());
|
||||||
|
}
|
||||||
|
QSqlQuery *ipCountQuery = prepareQuery(
|
||||||
|
QStringLiteral("SELECT user_name, COUNT(DISTINCT ip_address) FROM {prefix}_sessions WHERE user_name IN ") +
|
||||||
|
altInClause(QStringLiteral(":alt_ic"), resultNames) + sessionWindow +
|
||||||
|
QStringLiteral(" GROUP BY user_name"));
|
||||||
|
altBindValues(ipCountQuery, QStringLiteral(":alt_ic"), resultNames);
|
||||||
|
QMap<QString, int> ipCountsByUser;
|
||||||
|
if (execSqlQuery(ipCountQuery)) {
|
||||||
|
while (ipCountQuery->next()) {
|
||||||
|
ipCountsByUser.insert(ipCountQuery->value(0).toString(), ipCountQuery->value(1).toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (ServerInfo_UserAlt &alt : results) {
|
||||||
|
alt.set_ip_count(static_cast<google::protobuf::uint32>(
|
||||||
|
ipCountsByUser.value(QString::fromStdString(alt.user_name()), 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,12 @@ public:
|
||||||
const QString &beforeTime = QString(),
|
const QString &beforeTime = QString(),
|
||||||
bool *hasMore = nullptr);
|
bool *hasMore = nullptr);
|
||||||
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, int depth = 1);
|
QList<ServerInfo_UserAlt> getUserAlts(const QString &userName,
|
||||||
|
int depth = 1,
|
||||||
|
bool useEmail = true,
|
||||||
|
bool useClientId = true,
|
||||||
|
bool useIp = true,
|
||||||
|
int sessionMonths = 6);
|
||||||
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
QList<ServerInfo_ModeratorLogin> getModeratorLastLogins();
|
||||||
|
|
||||||
// Uptime snapshot as recorded by Servatrice::statusUpdate() into the
|
// Uptime snapshot as recorded by Servatrice::statusUpdate() into the
|
||||||
|
|
|
||||||
|
|
@ -2596,7 +2596,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetUserAlts(const Comma
|
||||||
|
|
||||||
Response_UserAlts *re = new Response_UserAlts;
|
Response_UserAlts *re = new Response_UserAlts;
|
||||||
const int depth = cmd.has_depth() ? static_cast<int>(cmd.depth()) : 1;
|
const int depth = cmd.has_depth() ? static_cast<int>(cmd.depth()) : 1;
|
||||||
const QList<ServerInfo_UserAlt> alts = sqlInterface->getUserAlts(userName, depth);
|
const int sessionMonths = cmd.has_session_months() ? static_cast<int>(cmd.session_months()) : 6;
|
||||||
|
const QList<ServerInfo_UserAlt> alts = sqlInterface->getUserAlts(
|
||||||
|
userName, depth, cmd.include_email(), cmd.include_clientid(), cmd.include_ip(), sessionMonths);
|
||||||
for (const ServerInfo_UserAlt &alt : alts) {
|
for (const ServerInfo_UserAlt &alt : alts) {
|
||||||
re->add_alts()->CopyFrom(alt);
|
re->add_alts()->CopyFrom(alt);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue