Expose WebUI ban counter to users

This commit is contained in:
Chocobo1 2020-02-12 18:51:38 +08:00 committed by sledgehammer999
parent 544d3f9025
commit b19153287b
No known key found for this signature in database
GPG key ID: 6E4A2D025B7CC9A2
8 changed files with 67 additions and 5 deletions

View file

@ -621,6 +621,16 @@ void Preferences::setWebUIPassword(const QByteArray &password)
setValue("Preferences/WebUI/Password_PBKDF2", password);
}
int Preferences::getWebUIMaxAuthFailCount() const
{
return value("Preferences/WebUI/MaxAuthenticationFailCount", 5).toInt();
}
void Preferences::setWebUIMaxAuthFailCount(const int count)
{
setValue("Preferences/WebUI/MaxAuthenticationFailCount", count);
}
int Preferences::getWebUISessionTimeout() const
{
return value("Preferences/WebUI/SessionTimeout", 3600).toInt();

View file

@ -194,6 +194,8 @@ public:
void setWebUiUsername(const QString &username);
QByteArray getWebUIPassword() const;
void setWebUIPassword(const QByteArray &password);
int getWebUIMaxAuthFailCount() const;
void setWebUIMaxAuthFailCount(int count);
int getWebUISessionTimeout() const;
void setWebUISessionTimeout(int timeout);

View file

@ -421,6 +421,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled);
connect(m_ui->spinBanCounter, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->spinSessionTimeout, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
@ -770,6 +771,7 @@ void OptionsDialog::saveOptions()
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked());
pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
pref->setWebUIMaxAuthFailCount(m_ui->spinBanCounter->value());
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
// Authentication
pref->setWebUiUsername(webUiUsername());
@ -1153,6 +1155,7 @@ void OptionsDialog::loadOptions()
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
m_ui->spinBanCounter->setValue(pref->getWebUIMaxAuthFailCount());
m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout());
// Security

View file

@ -2986,6 +2986,40 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_19">
<item>
<widget class="QLabel" name="lblBanCounter">
<property name="text">
<string>Ban client after consecutive failures:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBanCounter">
<property name="specialValueText">
<string>Never</string>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_15">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>

View file

@ -232,6 +232,7 @@ void AppController::preferencesAction()
for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUiAuthSubnetWhitelist()))
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
data["bypass_auth_subnet_whitelist"] = authSubnetWhitelistStringList.join("\n");
data["web_ui_max_auth_fail_count"] = pref->getWebUIMaxAuthFailCount();
data["web_ui_session_timeout"] = pref->getWebUISessionTimeout();
// Use alternative Web UI
data["alternative_webui_enabled"] = pref->isAltWebUiEnabled();
@ -601,6 +602,8 @@ void AppController::setPreferencesAction()
// recognize new lines and commas as delimiters
pref->setWebUiAuthSubnetWhitelist(it.value().toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts));
}
if (hasKey("web_ui_max_auth_fail_count"))
pref->setWebUIMaxAuthFailCount(it.value().toInt());
if (hasKey("web_ui_session_timeout"))
pref->setWebUISessionTimeout(it.value().toInt());
// Use alternative Web UI

View file

@ -38,7 +38,6 @@
#include "isessionmanager.h"
constexpr int BAN_TIME = 3600000; // 1 hour
constexpr int MAX_AUTH_FAILED_ATTEMPTS = 5;
void AuthController::loginAction()
{
@ -74,7 +73,8 @@ void AuthController::loginAction()
LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr));
}
else {
increaseFailedAttempts();
if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0)
increaseFailedAttempts();
setResult(QLatin1String("Fails."));
LogMsg(tr("WebAPI login failure. Reason: invalid credentials, attempt count: %1, IP: %2, username: %3")
.arg(QString::number(failedAttemptsCount()), clientAddr, usernameFromWeb)
@ -82,7 +82,7 @@ void AuthController::loginAction()
}
}
void AuthController::logoutAction()
void AuthController::logoutAction() const
{
sessionManager()->sessionEnd();
}
@ -108,10 +108,12 @@ int AuthController::failedAttemptsCount() const
void AuthController::increaseFailedAttempts()
{
Q_ASSERT(Preferences::instance()->getWebUIMaxAuthFailCount() > 0);
FailedLogin &failedLogin = m_clientFailedLogins[sessionManager()->clientId()];
++failedLogin.failedAttemptsCount;
if (failedLogin.failedAttemptsCount == MAX_AUTH_FAILED_ATTEMPTS) {
if (failedLogin.failedAttemptsCount >= Preferences::instance()->getWebUIMaxAuthFailCount()) {
// Max number of failed attempts reached
// Start ban period
failedLogin.bannedAt = QDateTime::currentMSecsSinceEpoch() / 1000;

View file

@ -44,7 +44,7 @@ public:
private slots:
void loginAction();
void logoutAction();
void logoutAction() const;
private:
bool isBanned() const;

View file

@ -729,6 +729,12 @@
<div class="formRow" style="padding-left: 30px; padding-top: 5px;">
<textarea id="bypass_auth_subnet_whitelist_textarea" rows="5" cols="48" placeholder="Example: 172.17.32.0/24, fdff:ffff:c8::/40"></textarea>
</div>
<table>
<tr>
<td><label for="webUIMaxAuthFailCountInput">QBT_TR(Ban client after consecutive failures:)QBT_TR[CONTEXT=OptionsDialog]</label></td>
<td><input type="number" id="webUIMaxAuthFailCountInput" style="width: 4em;" min="0" /></td>
</tr>
</table>
<table>
<tr>
<td><label for="webUISessionTimeoutInput">QBT_TR(Session timeout:)QBT_TR[CONTEXT=OptionsDialog]</label></td>
@ -1719,6 +1725,7 @@
$('bypass_auth_subnet_whitelist_checkbox').setProperty('checked', pref.bypass_auth_subnet_whitelist_enabled);
$('bypass_auth_subnet_whitelist_textarea').setProperty('value', pref.bypass_auth_subnet_whitelist);
updateBypasssAuthSettings();
$('webUIMaxAuthFailCountInput').setProperty('value', pref.web_ui_max_auth_fail_count.toInt());
$('webUISessionTimeoutInput').setProperty('value', pref.web_ui_session_timeout.toInt());
// Use alternative Web UI
@ -2082,6 +2089,7 @@
settings.set('bypass_local_auth', $('bypass_local_auth_checkbox').getProperty('checked'));
settings.set('bypass_auth_subnet_whitelist_enabled', $('bypass_auth_subnet_whitelist_checkbox').getProperty('checked'));
settings.set('bypass_auth_subnet_whitelist', $('bypass_auth_subnet_whitelist_textarea').getProperty('value'));
settings.set('web_ui_max_auth_fail_count', $('webUIMaxAuthFailCountInput').getProperty('value'));
settings.set('web_ui_session_timeout', $('webUISessionTimeoutInput').getProperty('value'));
// Use alternative Web UI