Drop WebUI default credentials

PR #19777.
This commit is contained in:
Vladimir Golovnev 2023-11-10 07:18:42 +03:00 committed by Vladimir Golovnev (Glassez)
parent 2c2252d7d9
commit 786c09e981
No known key found for this signature in database
GPG key ID: 52A2C7DEE2DFA6F7
21 changed files with 378 additions and 254 deletions

View file

@ -96,7 +96,6 @@
#include "gui/mainwindow.h" #include "gui/mainwindow.h"
#include "gui/shutdownconfirmdialog.h" #include "gui/shutdownconfirmdialog.h"
#include "gui/uithememanager.h" #include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/windowstate.h" #include "gui/windowstate.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -106,6 +105,9 @@
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
#include "webui/webui.h" #include "webui/webui.h"
#ifdef DISABLE_GUI
#include "base/utils/password.h"
#endif
#endif #endif
namespace namespace
@ -310,8 +312,8 @@ Application::Application(int &argc, char **argv)
if (isFileLoggerEnabled()) if (isFileLoggerEnabled())
m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType())); m_fileLogger = new FileLogger(fileLoggerPath(), isFileLoggerBackup(), fileLoggerMaxSize(), isFileLoggerDeleteOld(), fileLoggerAge(), static_cast<FileLogger::FileLogAgeType>(fileLoggerAgeType()));
if (m_commandLineArgs.webUiPort > 0) // it will be -1 when user did not set any value if (m_commandLineArgs.webUIPort > 0) // it will be -1 when user did not set any value
Preferences::instance()->setWebUiPort(m_commandLineArgs.webUiPort); Preferences::instance()->setWebUIPort(m_commandLineArgs.webUIPort);
if (m_commandLineArgs.torrentingPort > 0) // it will be -1 when user did not set any value if (m_commandLineArgs.torrentingPort > 0) // it will be -1 when user did not set any value
{ {
@ -899,25 +901,28 @@ int Application::exec()
#endif // DISABLE_GUI #endif // DISABLE_GUI
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
#ifndef DISABLE_GUI
m_webui = new WebUI(this); m_webui = new WebUI(this);
#ifdef DISABLE_GUI #else
const Preferences *pref = Preferences::instance();
const QString tempPassword = pref->getWebUIPassword().isEmpty()
? Utils::Password::generate() : QString();
m_webui = new WebUI(this, (!tempPassword.isEmpty() ? Utils::Password::PBKDF2::generate(tempPassword) : QByteArray()));
if (m_webui->isErrored()) if (m_webui->isErrored())
QCoreApplication::exit(EXIT_FAILURE); QCoreApplication::exit(EXIT_FAILURE);
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(EXIT_FAILURE); }); connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(EXIT_FAILURE); });
const Preferences *pref = Preferences::instance(); const auto scheme = pref->isWebUIHttpsEnabled() ? u"https"_s : u"http"_s;
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUIPort()));
const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_s : u"http"_s;
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUiPort()));
const QString mesg = u"\n******** %1 ********\n"_s.arg(tr("Information")) const QString mesg = u"\n******** %1 ********\n"_s.arg(tr("Information"))
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url); + tr("To control qBittorrent, access the WebUI at: %1").arg(url);
printf("%s\n", qUtf8Printable(mesg)); printf("%s\n", qUtf8Printable(mesg));
if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")) if (!tempPassword.isEmpty())
{ {
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n' const QString warning = tr("The WebUI administrator username is: %1").arg(pref->getWebUIUsername()) + u'\n'
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_s) + u'\n' + tr("The WebUI administrator password was not set. A temporary password is provided for this session: %1").arg(tempPassword) + u'\n'
+ tr("This is a security risk, please change your password in program preferences.") + u'\n'; + tr("You should set your own password in program preferences.") + u'\n';
printf("%s", qUtf8Printable(warning)); printf("%s", qUtf8Printable(warning));
} }
#endif // DISABLE_GUI #endif // DISABLE_GUI
@ -1300,3 +1305,10 @@ void Application::cleanup()
Utils::Misc::shutdownComputer(m_shutdownAct); Utils::Misc::shutdownComputer(m_shutdownAct);
} }
} }
#ifndef DISABLE_WEBUI
WebUI *Application::webUI() const
{
return m_webui;
}
#endif

View file

@ -149,6 +149,10 @@ private slots:
#endif #endif
private: private:
#ifndef DISABLE_WEBUI
WebUI *webUI() const override;
#endif
void initializeTranslation(); void initializeTranslation();
void processParams(const QBtCommandLineParameters &params); void processParams(const QBtCommandLineParameters &params);
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const; void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;

View file

@ -349,7 +349,7 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
#elif !defined(Q_OS_WIN) #elif !defined(Q_OS_WIN)
, shouldDaemonize(DAEMON_OPTION.value(env)) , shouldDaemonize(DAEMON_OPTION.value(env))
#endif #endif
, webUiPort(WEBUI_PORT_OPTION.value(env, -1)) , webUIPort(WEBUI_PORT_OPTION.value(env, -1))
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1)) , torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
, skipDialog(SKIP_DIALOG_OPTION.value(env)) , skipDialog(SKIP_DIALOG_OPTION.value(env))
, profileDir(PROFILE_OPTION.value(env)) , profileDir(PROFILE_OPTION.value(env))
@ -387,8 +387,8 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
#endif #endif
else if (arg == WEBUI_PORT_OPTION) else if (arg == WEBUI_PORT_OPTION)
{ {
result.webUiPort = WEBUI_PORT_OPTION.value(arg); result.webUIPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535)) if ((result.webUIPort < 1) || (result.webUIPort > 65535))
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).") throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_s)); .arg(u"--webui-port"_s));
} }
@ -509,7 +509,7 @@ QString makeUsage(const QString &prgName)
#endif #endif
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n' + SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port")) + WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port")) + wrapText(QCoreApplication::translate("CMD Options", "Change the WebUI port"))
+ u'\n' + u'\n'
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port")) + TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port")) + wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))

View file

@ -53,7 +53,7 @@ struct QBtCommandLineParameters
#elif !defined(Q_OS_WIN) #elif !defined(Q_OS_WIN)
bool shouldDaemonize = false; bool shouldDaemonize = false;
#endif #endif
int webUiPort = -1; int webUIPort = -1;
int torrentingPort = -1; int torrentingPort = -1;
std::optional<bool> skipDialog; std::optional<bool> skipDialog;
Path profileDir; Path profileDir;

View file

@ -36,6 +36,7 @@
class QString; class QString;
class Path; class Path;
class WebUI;
struct QBtCommandLineParameters; struct QBtCommandLineParameters;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@ -83,4 +84,8 @@ public:
virtual MemoryPriority processMemoryPriority() const = 0; virtual MemoryPriority processMemoryPriority() const = 0;
virtual void setProcessMemoryPriority(MemoryPriority priority) = 0; virtual void setProcessMemoryPriority(MemoryPriority priority) = 0;
#endif #endif
#ifndef DISABLE_WEBUI
virtual WebUI *webUI() const = 0;
#endif
}; };

View file

@ -628,7 +628,7 @@ void Preferences::setSearchEnabled(const bool enabled)
setValue(u"Preferences/Search/SearchEnabled"_s, enabled); setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
} }
bool Preferences::isWebUiEnabled() const bool Preferences::isWebUIEnabled() const
{ {
#ifdef DISABLE_GUI #ifdef DISABLE_GUI
const bool defaultValue = true; const bool defaultValue = true;
@ -638,41 +638,41 @@ bool Preferences::isWebUiEnabled() const
return value(u"Preferences/WebUI/Enabled"_s, defaultValue); return value(u"Preferences/WebUI/Enabled"_s, defaultValue);
} }
void Preferences::setWebUiEnabled(const bool enabled) void Preferences::setWebUIEnabled(const bool enabled)
{ {
if (enabled == isWebUiEnabled()) if (enabled == isWebUIEnabled())
return; return;
setValue(u"Preferences/WebUI/Enabled"_s, enabled); setValue(u"Preferences/WebUI/Enabled"_s, enabled);
} }
bool Preferences::isWebUiLocalAuthEnabled() const bool Preferences::isWebUILocalAuthEnabled() const
{ {
return value(u"Preferences/WebUI/LocalHostAuth"_s, true); return value(u"Preferences/WebUI/LocalHostAuth"_s, true);
} }
void Preferences::setWebUiLocalAuthEnabled(const bool enabled) void Preferences::setWebUILocalAuthEnabled(const bool enabled)
{ {
if (enabled == isWebUiLocalAuthEnabled()) if (enabled == isWebUILocalAuthEnabled())
return; return;
setValue(u"Preferences/WebUI/LocalHostAuth"_s, enabled); setValue(u"Preferences/WebUI/LocalHostAuth"_s, enabled);
} }
bool Preferences::isWebUiAuthSubnetWhitelistEnabled() const bool Preferences::isWebUIAuthSubnetWhitelistEnabled() const
{ {
return value(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, false); return value(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, false);
} }
void Preferences::setWebUiAuthSubnetWhitelistEnabled(const bool enabled) void Preferences::setWebUIAuthSubnetWhitelistEnabled(const bool enabled)
{ {
if (enabled == isWebUiAuthSubnetWhitelistEnabled()) if (enabled == isWebUIAuthSubnetWhitelistEnabled())
return; return;
setValue(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, enabled); setValue(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, enabled);
} }
QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const QVector<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
{ {
const auto subnets = value<QStringList>(u"Preferences/WebUI/AuthSubnetWhitelist"_s); const auto subnets = value<QStringList>(u"Preferences/WebUI/AuthSubnetWhitelist"_s);
@ -689,7 +689,7 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
return ret; return ret;
} }
void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets) void Preferences::setWebUIAuthSubnetWhitelist(QStringList subnets)
{ {
Algorithm::removeIf(subnets, [](const QString &subnet) Algorithm::removeIf(subnets, [](const QString &subnet)
{ {
@ -712,27 +712,27 @@ void Preferences::setServerDomains(const QString &str)
setValue(u"Preferences/WebUI/ServerDomains"_s, str); setValue(u"Preferences/WebUI/ServerDomains"_s, str);
} }
QString Preferences::getWebUiAddress() const QString Preferences::getWebUIAddress() const
{ {
return value<QString>(u"Preferences/WebUI/Address"_s, u"*"_s).trimmed(); return value<QString>(u"Preferences/WebUI/Address"_s, u"*"_s).trimmed();
} }
void Preferences::setWebUiAddress(const QString &addr) void Preferences::setWebUIAddress(const QString &addr)
{ {
if (addr == getWebUiAddress()) if (addr == getWebUIAddress())
return; return;
setValue(u"Preferences/WebUI/Address"_s, addr.trimmed()); setValue(u"Preferences/WebUI/Address"_s, addr.trimmed());
} }
quint16 Preferences::getWebUiPort() const quint16 Preferences::getWebUIPort() const
{ {
return value<quint16>(u"Preferences/WebUI/Port"_s, 8080); return value<quint16>(u"Preferences/WebUI/Port"_s, 8080);
} }
void Preferences::setWebUiPort(const quint16 port) void Preferences::setWebUIPort(const quint16 port)
{ {
if (port == getWebUiPort()) if (port == getWebUIPort())
return; return;
// cast to `int` type so it will show human readable unit in configuration file // cast to `int` type so it will show human readable unit in configuration file
@ -752,14 +752,14 @@ void Preferences::setUPnPForWebUIPort(const bool enabled)
setValue(u"Preferences/WebUI/UseUPnP"_s, enabled); setValue(u"Preferences/WebUI/UseUPnP"_s, enabled);
} }
QString Preferences::getWebUiUsername() const QString Preferences::getWebUIUsername() const
{ {
return value<QString>(u"Preferences/WebUI/Username"_s, u"admin"_s); return value<QString>(u"Preferences/WebUI/Username"_s, u"admin"_s);
} }
void Preferences::setWebUiUsername(const QString &username) void Preferences::setWebUIUsername(const QString &username)
{ {
if (username == getWebUiUsername()) if (username == getWebUIUsername())
return; return;
setValue(u"Preferences/WebUI/Username"_s, username); setValue(u"Preferences/WebUI/Username"_s, username);
@ -767,9 +767,7 @@ void Preferences::setWebUiUsername(const QString &username)
QByteArray Preferences::getWebUIPassword() const QByteArray Preferences::getWebUIPassword() const
{ {
// default: adminadmin return value<QByteArray>(u"Preferences/WebUI/Password_PBKDF2"_s);
const auto defaultValue = QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==");
return value(u"Preferences/WebUI/Password_PBKDF2"_s, defaultValue);
} }
void Preferences::setWebUIPassword(const QByteArray &password) void Preferences::setWebUIPassword(const QByteArray &password)
@ -832,40 +830,40 @@ void Preferences::setWebAPISessionCookieName(const QString &cookieName)
setValue(u"WebAPI/SessionCookieName"_s, cookieName); setValue(u"WebAPI/SessionCookieName"_s, cookieName);
} }
bool Preferences::isWebUiClickjackingProtectionEnabled() const bool Preferences::isWebUIClickjackingProtectionEnabled() const
{ {
return value(u"Preferences/WebUI/ClickjackingProtection"_s, true); return value(u"Preferences/WebUI/ClickjackingProtection"_s, true);
} }
void Preferences::setWebUiClickjackingProtectionEnabled(const bool enabled) void Preferences::setWebUIClickjackingProtectionEnabled(const bool enabled)
{ {
if (enabled == isWebUiClickjackingProtectionEnabled()) if (enabled == isWebUIClickjackingProtectionEnabled())
return; return;
setValue(u"Preferences/WebUI/ClickjackingProtection"_s, enabled); setValue(u"Preferences/WebUI/ClickjackingProtection"_s, enabled);
} }
bool Preferences::isWebUiCSRFProtectionEnabled() const bool Preferences::isWebUICSRFProtectionEnabled() const
{ {
return value(u"Preferences/WebUI/CSRFProtection"_s, true); return value(u"Preferences/WebUI/CSRFProtection"_s, true);
} }
void Preferences::setWebUiCSRFProtectionEnabled(const bool enabled) void Preferences::setWebUICSRFProtectionEnabled(const bool enabled)
{ {
if (enabled == isWebUiCSRFProtectionEnabled()) if (enabled == isWebUICSRFProtectionEnabled())
return; return;
setValue(u"Preferences/WebUI/CSRFProtection"_s, enabled); setValue(u"Preferences/WebUI/CSRFProtection"_s, enabled);
} }
bool Preferences::isWebUiSecureCookieEnabled() const bool Preferences::isWebUISecureCookieEnabled() const
{ {
return value(u"Preferences/WebUI/SecureCookie"_s, true); return value(u"Preferences/WebUI/SecureCookie"_s, true);
} }
void Preferences::setWebUiSecureCookieEnabled(const bool enabled) void Preferences::setWebUISecureCookieEnabled(const bool enabled)
{ {
if (enabled == isWebUiSecureCookieEnabled()) if (enabled == isWebUISecureCookieEnabled())
return; return;
setValue(u"Preferences/WebUI/SecureCookie"_s, enabled); setValue(u"Preferences/WebUI/SecureCookie"_s, enabled);
@ -884,14 +882,14 @@ void Preferences::setWebUIHostHeaderValidationEnabled(const bool enabled)
setValue(u"Preferences/WebUI/HostHeaderValidation"_s, enabled); setValue(u"Preferences/WebUI/HostHeaderValidation"_s, enabled);
} }
bool Preferences::isWebUiHttpsEnabled() const bool Preferences::isWebUIHttpsEnabled() const
{ {
return value(u"Preferences/WebUI/HTTPS/Enabled"_s, false); return value(u"Preferences/WebUI/HTTPS/Enabled"_s, false);
} }
void Preferences::setWebUiHttpsEnabled(const bool enabled) void Preferences::setWebUIHttpsEnabled(const bool enabled)
{ {
if (enabled == isWebUiHttpsEnabled()) if (enabled == isWebUIHttpsEnabled())
return; return;
setValue(u"Preferences/WebUI/HTTPS/Enabled"_s, enabled); setValue(u"Preferences/WebUI/HTTPS/Enabled"_s, enabled);
@ -923,27 +921,27 @@ void Preferences::setWebUIHttpsKeyPath(const Path &path)
setValue(u"Preferences/WebUI/HTTPS/KeyPath"_s, path); setValue(u"Preferences/WebUI/HTTPS/KeyPath"_s, path);
} }
bool Preferences::isAltWebUiEnabled() const bool Preferences::isAltWebUIEnabled() const
{ {
return value(u"Preferences/WebUI/AlternativeUIEnabled"_s, false); return value(u"Preferences/WebUI/AlternativeUIEnabled"_s, false);
} }
void Preferences::setAltWebUiEnabled(const bool enabled) void Preferences::setAltWebUIEnabled(const bool enabled)
{ {
if (enabled == isAltWebUiEnabled()) if (enabled == isAltWebUIEnabled())
return; return;
setValue(u"Preferences/WebUI/AlternativeUIEnabled"_s, enabled); setValue(u"Preferences/WebUI/AlternativeUIEnabled"_s, enabled);
} }
Path Preferences::getWebUiRootFolder() const Path Preferences::getWebUIRootFolder() const
{ {
return value<Path>(u"Preferences/WebUI/RootFolder"_s); return value<Path>(u"Preferences/WebUI/RootFolder"_s);
} }
void Preferences::setWebUiRootFolder(const Path &path) void Preferences::setWebUIRootFolder(const Path &path)
{ {
if (path == getWebUiRootFolder()) if (path == getWebUIRootFolder())
return; return;
setValue(u"Preferences/WebUI/RootFolder"_s, path); setValue(u"Preferences/WebUI/RootFolder"_s, path);

View file

@ -169,26 +169,26 @@ public:
void setSearchEnabled(bool enabled); void setSearchEnabled(bool enabled);
// HTTP Server // HTTP Server
bool isWebUiEnabled() const; bool isWebUIEnabled() const;
void setWebUiEnabled(bool enabled); void setWebUIEnabled(bool enabled);
QString getServerDomains() const; QString getServerDomains() const;
void setServerDomains(const QString &str); void setServerDomains(const QString &str);
QString getWebUiAddress() const; QString getWebUIAddress() const;
void setWebUiAddress(const QString &addr); void setWebUIAddress(const QString &addr);
quint16 getWebUiPort() const; quint16 getWebUIPort() const;
void setWebUiPort(quint16 port); void setWebUIPort(quint16 port);
bool useUPnPForWebUIPort() const; bool useUPnPForWebUIPort() const;
void setUPnPForWebUIPort(bool enabled); void setUPnPForWebUIPort(bool enabled);
// Authentication // Authentication
bool isWebUiLocalAuthEnabled() const; bool isWebUILocalAuthEnabled() const;
void setWebUiLocalAuthEnabled(bool enabled); void setWebUILocalAuthEnabled(bool enabled);
bool isWebUiAuthSubnetWhitelistEnabled() const; bool isWebUIAuthSubnetWhitelistEnabled() const;
void setWebUiAuthSubnetWhitelistEnabled(bool enabled); void setWebUIAuthSubnetWhitelistEnabled(bool enabled);
QVector<Utils::Net::Subnet> getWebUiAuthSubnetWhitelist() const; QVector<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
void setWebUiAuthSubnetWhitelist(QStringList subnets); void setWebUIAuthSubnetWhitelist(QStringList subnets);
QString getWebUiUsername() const; QString getWebUIUsername() const;
void setWebUiUsername(const QString &username); void setWebUIUsername(const QString &username);
QByteArray getWebUIPassword() const; QByteArray getWebUIPassword() const;
void setWebUIPassword(const QByteArray &password); void setWebUIPassword(const QByteArray &password);
int getWebUIMaxAuthFailCount() const; int getWebUIMaxAuthFailCount() const;
@ -201,26 +201,26 @@ public:
void setWebAPISessionCookieName(const QString &cookieName); void setWebAPISessionCookieName(const QString &cookieName);
// WebUI security // WebUI security
bool isWebUiClickjackingProtectionEnabled() const; bool isWebUIClickjackingProtectionEnabled() const;
void setWebUiClickjackingProtectionEnabled(bool enabled); void setWebUIClickjackingProtectionEnabled(bool enabled);
bool isWebUiCSRFProtectionEnabled() const; bool isWebUICSRFProtectionEnabled() const;
void setWebUiCSRFProtectionEnabled(bool enabled); void setWebUICSRFProtectionEnabled(bool enabled);
bool isWebUiSecureCookieEnabled () const; bool isWebUISecureCookieEnabled () const;
void setWebUiSecureCookieEnabled(bool enabled); void setWebUISecureCookieEnabled(bool enabled);
bool isWebUIHostHeaderValidationEnabled() const; bool isWebUIHostHeaderValidationEnabled() const;
void setWebUIHostHeaderValidationEnabled(bool enabled); void setWebUIHostHeaderValidationEnabled(bool enabled);
// HTTPS // HTTPS
bool isWebUiHttpsEnabled() const; bool isWebUIHttpsEnabled() const;
void setWebUiHttpsEnabled(bool enabled); void setWebUIHttpsEnabled(bool enabled);
Path getWebUIHttpsCertificatePath() const; Path getWebUIHttpsCertificatePath() const;
void setWebUIHttpsCertificatePath(const Path &path); void setWebUIHttpsCertificatePath(const Path &path);
Path getWebUIHttpsKeyPath() const; Path getWebUIHttpsKeyPath() const;
void setWebUIHttpsKeyPath(const Path &path); void setWebUIHttpsKeyPath(const Path &path);
bool isAltWebUiEnabled() const; bool isAltWebUIEnabled() const;
void setAltWebUiEnabled(bool enabled); void setAltWebUIEnabled(bool enabled);
Path getWebUiRootFolder() const; Path getWebUIRootFolder() const;
void setWebUiRootFolder(const Path &path); void setWebUIRootFolder(const Path &path);
// WebUI custom HTTP headers // WebUI custom HTTP headers
bool isWebUICustomHTTPHeadersEnabled() const; bool isWebUICustomHTTPHeadersEnabled() const;

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2018 Mike Tzou (Chocobo1)
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -36,6 +37,7 @@
#include <QString> #include <QString>
#include <QVector> #include <QVector>
#include "base/global.h"
#include "bytearray.h" #include "bytearray.h"
#include "random.h" #include "random.h"
@ -65,6 +67,21 @@ bool Utils::Password::slowEquals(const QByteArray &a, const QByteArray &b)
return (diff == 0); return (diff == 0);
} }
QString Utils::Password::generate()
{
const QString alphanum = u"23456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"_s;
const int passwordLength = 9;
QString pass;
pass.reserve(passwordLength);
while (pass.length() < passwordLength)
{
const auto num = Utils::Random::rand(0, (alphanum.size() - 1));
pass.append(alphanum[num]);
}
return pass;
}
QByteArray Utils::Password::PBKDF2::generate(const QString &password) QByteArray Utils::Password::PBKDF2::generate(const QString &password)
{ {
return generate(password.toUtf8()); return generate(password.toUtf8());
@ -72,9 +89,8 @@ QByteArray Utils::Password::PBKDF2::generate(const QString &password)
QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password) QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password)
{ {
const std::array<uint32_t, 4> salt const std::array<uint32_t, 4> salt {
{{Random::rand(), Random::rand() {Random::rand(), Random::rand(), Random::rand(), Random::rand()}};
, Random::rand(), Random::rand()}};
std::array<unsigned char, 64> outBuf {}; std::array<unsigned char, 64> outBuf {};
const int hmacResult = PKCS5_PBKDF2_HMAC(password.constData(), password.size() const int hmacResult = PKCS5_PBKDF2_HMAC(password.constData(), password.size()

View file

@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1) * Copyright (C) 2018 Mike Tzou (Chocobo1)
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -37,6 +38,8 @@ namespace Utils::Password
// Taken from https://crackstation.net/hashing-security.htm // Taken from https://crackstation.net/hashing-security.htm
bool slowEquals(const QByteArray &a, const QByteArray &b); bool slowEquals(const QByteArray &a, const QByteArray &b);
QString generate();
namespace PBKDF2 namespace PBKDF2
{ {
QByteArray generate(const QString &password); QByteArray generate(const QString &password);

View file

@ -50,7 +50,7 @@ IPSubnetWhitelistOptionsDialog::IPSubnetWhitelistOptionsDialog(QWidget *parent)
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QStringList authSubnetWhitelistStringList; QStringList authSubnetWhitelistStringList;
for (const Utils::Net::Subnet &subnet : asConst(Preferences::instance()->getWebUiAuthSubnetWhitelist())) for (const Utils::Net::Subnet &subnet : asConst(Preferences::instance()->getWebUIAuthSubnetWhitelist()))
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet); authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
m_model = new QStringListModel(authSubnetWhitelistStringList, this); m_model = new QStringListModel(authSubnetWhitelistStringList, this);
@ -81,7 +81,7 @@ void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
// Operate on the m_sortFilter to grab the strings in sorted order // Operate on the m_sortFilter to grab the strings in sorted order
for (int i = 0; i < m_sortFilter->rowCount(); ++i) for (int i = 0; i < m_sortFilter->rowCount(); ++i)
subnets.append(m_sortFilter->index(i, 0).data().toString()); subnets.append(m_sortFilter->index(i, 0).data().toString());
Preferences::instance()->setWebUiAuthSubnetWhitelist(subnets); Preferences::instance()->setWebUIAuthSubnetWhitelist(subnets);
QDialog::accept(); QDialog::accept();
} }
else else

View file

@ -1182,7 +1182,7 @@ void MainWindow::closeEvent(QCloseEvent *e)
if (!isVisible()) if (!isVisible())
show(); show();
QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"), QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
// Split it because the last sentence is used in the Web UI // Split it because the last sentence is used in the WebUI
tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"), tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
QMessageBox::NoButton, this); QMessageBox::NoButton, this);
QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole); QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);

View file

@ -69,6 +69,7 @@
#include "utils.h" #include "utils.h"
#include "watchedfolderoptionsdialog.h" #include "watchedfolderoptionsdialog.h"
#include "watchedfoldersmodel.h" #include "watchedfoldersmodel.h"
#include "webui/webui.h"
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
#include "base/net/dnsupdater.h" #include "base/net/dnsupdater.h"
@ -80,6 +81,9 @@
#define SETTINGS_KEY(name) u"OptionsDialog/" name #define SETTINGS_KEY(name) u"OptionsDialog/" name
const int WEBUI_MIN_USERNAME_LENGTH = 3;
const int WEBUI_MIN_PASSWORD_LENGTH = 6;
namespace namespace
{ {
QStringList translatedWeekdayNames() QStringList translatedWeekdayNames()
@ -106,6 +110,16 @@ namespace
} }
}; };
bool isValidWebUIUsername(const QString &username)
{
return (username.length() >= WEBUI_MIN_USERNAME_LENGTH);
}
bool isValidWebUIPassword(const QString &password)
{
return (password.length() >= WEBUI_MIN_PASSWORD_LENGTH);
}
// Shortcuts for frequently used signals that have more than one overload. They would require // Shortcuts for frequently used signals that have more than one overload. They would require
// type casts and that is why we declare required member pointer here instead. // type casts and that is why we declare required member pointer here instead.
void (QComboBox::*qComboBoxCurrentIndexChanged)(int) = &QComboBox::currentIndexChanged; void (QComboBox::*qComboBoxCurrentIndexChanged)(int) = &QComboBox::currentIndexChanged;
@ -175,7 +189,11 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
// setup apply button // setup apply button
m_applyButton->setEnabled(false); m_applyButton->setEnabled(false);
connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings); connect(m_applyButton, &QPushButton::clicked, this, [this]
{
if (applySettings())
m_applyButton->setEnabled(false);
});
// disable mouse wheel event on widgets to avoid misselection // disable mouse wheel event on widgets to avoid misselection
auto *wheelEventEater = new WheelEventEater(this); auto *wheelEventEater = new WheelEventEater(this);
@ -1211,28 +1229,33 @@ void OptionsDialog::loadWebUITabOptions()
m_ui->textWebUIRootFolder->setMode(FileSystemPathEdit::Mode::DirectoryOpen); m_ui->textWebUIRootFolder->setMode(FileSystemPathEdit::Mode::DirectoryOpen);
m_ui->textWebUIRootFolder->setDialogCaption(tr("Choose Alternative UI files location")); m_ui->textWebUIRootFolder->setDialogCaption(tr("Choose Alternative UI files location"));
m_ui->checkWebUi->setChecked(pref->isWebUiEnabled()); if (app()->webUI()->isErrored())
m_ui->textWebUiAddress->setText(pref->getWebUiAddress()); m_ui->labelWebUIError->setText(tr("WebUI configuration failed. Reason: %1").arg(app()->webUI()->errorMessage()));
m_ui->spinWebUiPort->setValue(pref->getWebUiPort()); else
m_ui->labelWebUIError->hide();
m_ui->checkWebUI->setChecked(pref->isWebUIEnabled());
m_ui->textWebUIAddress->setText(pref->getWebUIAddress());
m_ui->spinWebUIPort->setValue(pref->getWebUIPort());
m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort()); m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort());
m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled()); m_ui->checkWebUIHttps->setChecked(pref->isWebUIHttpsEnabled());
webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath()); webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath());
webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath()); webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath());
m_ui->textWebUiUsername->setText(pref->getWebUiUsername()); m_ui->textWebUIUsername->setText(pref->getWebUIUsername());
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled()); m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUILocalAuthEnabled());
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled()); m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUIAuthSubnetWhitelistEnabled());
m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
m_ui->spinBanCounter->setValue(pref->getWebUIMaxAuthFailCount()); m_ui->spinBanCounter->setValue(pref->getWebUIMaxAuthFailCount());
m_ui->spinBanDuration->setValue(pref->getWebUIBanDuration().count()); m_ui->spinBanDuration->setValue(pref->getWebUIBanDuration().count());
m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout()); m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout());
// Alternative UI // Alternative UI
m_ui->groupAltWebUI->setChecked(pref->isAltWebUiEnabled()); m_ui->groupAltWebUI->setChecked(pref->isAltWebUIEnabled());
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUiRootFolder()); m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUIRootFolder());
// Security // Security
m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled()); m_ui->checkClickjacking->setChecked(pref->isWebUIClickjackingProtectionEnabled());
m_ui->checkCSRFProtection->setChecked(pref->isWebUiCSRFProtectionEnabled()); m_ui->checkCSRFProtection->setChecked(pref->isWebUICSRFProtectionEnabled());
m_ui->checkSecureCookie->setEnabled(pref->isWebUiHttpsEnabled()); m_ui->checkSecureCookie->setEnabled(pref->isWebUIHttpsEnabled());
m_ui->checkSecureCookie->setChecked(pref->isWebUiSecureCookieEnabled()); m_ui->checkSecureCookie->setChecked(pref->isWebUISecureCookieEnabled());
m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled()); m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
m_ui->textServerDomains->setText(pref->getServerDomains()); m_ui->textServerDomains->setText(pref->getServerDomains());
// Custom HTTP headers // Custom HTTP headers
@ -1248,18 +1271,18 @@ void OptionsDialog::loadWebUITabOptions()
m_ui->DNSUsernameTxt->setText(pref->getDynDNSUsername()); m_ui->DNSUsernameTxt->setText(pref->getDynDNSUsername());
m_ui->DNSPasswordTxt->setText(pref->getDynDNSPassword()); m_ui->DNSPasswordTxt->setText(pref->getDynDNSPassword());
connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUI, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->spinWebUIPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUIHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsCertChanged); connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsCertChanged);
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsKeyChanged); connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsKeyChanged);
connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@ -1273,7 +1296,7 @@ void OptionsDialog::loadWebUITabOptions()
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, m_ui->checkSecureCookie, &QWidget::setEnabled); connect(m_ui->checkWebUIHttps, &QGroupBox::toggled, m_ui->checkSecureCookie, &QWidget::setEnabled);
connect(m_ui->checkSecureCookie, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkSecureCookie, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
@ -1295,29 +1318,32 @@ void OptionsDialog::saveWebUITabOptions() const
{ {
auto *pref = Preferences::instance(); auto *pref = Preferences::instance();
pref->setWebUiEnabled(isWebUiEnabled()); const bool webUIEnabled = isWebUIEnabled();
pref->setWebUiAddress(m_ui->textWebUiAddress->text());
pref->setWebUiPort(m_ui->spinWebUiPort->value()); pref->setWebUIEnabled(webUIEnabled);
pref->setWebUIAddress(m_ui->textWebUIAddress->text());
pref->setWebUIPort(m_ui->spinWebUIPort->value());
pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked()); pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked());
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked()); pref->setWebUIHttpsEnabled(m_ui->checkWebUIHttps->isChecked());
pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath()); pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath()); pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
pref->setWebUIMaxAuthFailCount(m_ui->spinBanCounter->value()); pref->setWebUIMaxAuthFailCount(m_ui->spinBanCounter->value());
pref->setWebUIBanDuration(std::chrono::seconds {m_ui->spinBanDuration->value()}); pref->setWebUIBanDuration(std::chrono::seconds {m_ui->spinBanDuration->value()});
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value()); pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
// Authentication // Authentication
pref->setWebUiUsername(webUiUsername()); if (const QString username = webUIUsername(); isValidWebUIUsername(username))
if (!webUiPassword().isEmpty()) pref->setWebUIUsername(username);
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(webUiPassword())); if (const QString password = webUIPassword(); isValidWebUIPassword(password))
pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked()); pref->setWebUIPassword(Utils::Password::PBKDF2::generate(password));
pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); pref->setWebUILocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
pref->setWebUIAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
// Alternative UI // Alternative UI
pref->setAltWebUiEnabled(m_ui->groupAltWebUI->isChecked()); pref->setAltWebUIEnabled(m_ui->groupAltWebUI->isChecked());
pref->setWebUiRootFolder(m_ui->textWebUIRootFolder->selectedPath()); pref->setWebUIRootFolder(m_ui->textWebUIRootFolder->selectedPath());
// Security // Security
pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked()); pref->setWebUIClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
pref->setWebUiCSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked()); pref->setWebUICSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
pref->setWebUiSecureCookieEnabled(m_ui->checkSecureCookie->isChecked()); pref->setWebUISecureCookieEnabled(m_ui->checkSecureCookie->isChecked());
pref->setWebUIHostHeaderValidationEnabled(m_ui->groupHostHeaderValidation->isChecked()); pref->setWebUIHostHeaderValidationEnabled(m_ui->groupHostHeaderValidation->isChecked());
pref->setServerDomains(m_ui->textServerDomains->text()); pref->setServerDomains(m_ui->textServerDomains->text());
// Custom HTTP headers // Custom HTTP headers
@ -1517,53 +1543,37 @@ void OptionsDialog::on_buttonBox_accepted()
{ {
if (m_applyButton->isEnabled()) if (m_applyButton->isEnabled())
{ {
if (!schedTimesOk()) if (!applySettings())
{
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
return; return;
}
#ifndef DISABLE_WEBUI
if (!webUIAuthenticationOk())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
if (!isAlternativeWebUIPathValid())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
#endif
m_applyButton->setEnabled(false); m_applyButton->setEnabled(false);
saveOptions();
} }
accept(); accept();
} }
void OptionsDialog::applySettings() bool OptionsDialog::applySettings()
{ {
if (!schedTimesOk()) if (!schedTimesOk())
{ {
m_ui->tabSelection->setCurrentRow(TAB_SPEED); m_ui->tabSelection->setCurrentRow(TAB_SPEED);
return; return false;
} }
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
if (!webUIAuthenticationOk()) if (isWebUIEnabled() && !webUIAuthenticationOk())
{ {
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return false;
} }
if (!isAlternativeWebUIPathValid()) if (!isAlternativeWebUIPathValid())
{ {
m_ui->tabSelection->setCurrentRow(TAB_WEBUI); m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return; return false;
} }
#endif #endif
m_applyButton->setEnabled(false);
saveOptions(); saveOptions();
return true;
} }
void OptionsDialog::on_buttonBox_rejected() void OptionsDialog::on_buttonBox_rejected()
@ -1859,31 +1869,33 @@ void OptionsDialog::webUIHttpsKeyChanged(const Path &path)
(isKeyValid ? u"security-high"_s : u"security-low"_s), 24)); (isKeyValid ? u"security-high"_s : u"security-low"_s), 24));
} }
bool OptionsDialog::isWebUiEnabled() const bool OptionsDialog::isWebUIEnabled() const
{ {
return m_ui->checkWebUi->isChecked(); return m_ui->checkWebUI->isChecked();
} }
QString OptionsDialog::webUiUsername() const QString OptionsDialog::webUIUsername() const
{ {
return m_ui->textWebUiUsername->text(); return m_ui->textWebUIUsername->text();
} }
QString OptionsDialog::webUiPassword() const QString OptionsDialog::webUIPassword() const
{ {
return m_ui->textWebUiPassword->text(); return m_ui->textWebUIPassword->text();
} }
bool OptionsDialog::webUIAuthenticationOk() bool OptionsDialog::webUIAuthenticationOk()
{ {
if (webUiUsername().length() < 3) if (!isValidWebUIUsername(webUIUsername()))
{ {
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI username must be at least 3 characters long.")); QMessageBox::warning(this, tr("Length Error"), tr("The WebUI username must be at least 3 characters long."));
return false; return false;
} }
if (!webUiPassword().isEmpty() && (webUiPassword().length() < 6))
const bool dontChangePassword = webUIPassword().isEmpty() && !Preferences::instance()->getWebUIPassword().isEmpty();
if (!isValidWebUIPassword(webUIPassword()) && !dontChangePassword)
{ {
QMessageBox::warning(this, tr("Length Error"), tr("The Web UI password must be at least 6 characters long.")); QMessageBox::warning(this, tr("Length Error"), tr("The WebUI password must be at least 6 characters long."));
return false; return false;
} }
return true; return true;
@ -1893,7 +1905,7 @@ bool OptionsDialog::isAlternativeWebUIPathValid()
{ {
if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().isEmpty()) if (m_ui->groupAltWebUI->isChecked() && m_ui->textWebUIRootFolder->selectedPath().isEmpty())
{ {
QMessageBox::warning(this, tr("Location Error"), tr("The alternative Web UI files location cannot be blank.")); QMessageBox::warning(this, tr("Location Error"), tr("The alternative WebUI files location cannot be blank."));
return false; return false;
} }
return true; return true;

View file

@ -88,7 +88,6 @@ private slots:
void adjustProxyOptions(); void adjustProxyOptions();
void on_buttonBox_accepted(); void on_buttonBox_accepted();
void on_buttonBox_rejected(); void on_buttonBox_rejected();
void applySettings();
void enableApplyButton(); void enableApplyButton();
void toggleComboRatioLimitAct(); void toggleComboRatioLimitAct();
void changePage(QListWidgetItem *, QListWidgetItem *); void changePage(QListWidgetItem *, QListWidgetItem *);
@ -115,6 +114,7 @@ private:
void showEvent(QShowEvent *e) override; void showEvent(QShowEvent *e) override;
// Methods // Methods
bool applySettings();
void saveOptions() const; void saveOptions() const;
void loadBehaviorTabOptions(); void loadBehaviorTabOptions();
@ -184,9 +184,9 @@ private:
int getMaxActiveTorrents() const; int getMaxActiveTorrents() const;
// WebUI // WebUI
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
bool isWebUiEnabled() const; bool isWebUIEnabled() const;
QString webUiUsername() const; QString webUIUsername() const;
QString webUiPassword() const; QString webUIPassword() const;
bool webUIAuthenticationOk(); bool webUIAuthenticationOk();
bool isAlternativeWebUIPathValid(); bool isAlternativeWebUIPathValid();
#endif #endif

View file

@ -3223,8 +3223,8 @@ Disable encryption: Only connect to peers without protocol encryption</string>
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabWebuiPage"> <widget class="QWidget" name="tabWebUIPage">
<layout class="QVBoxLayout" name="tabWebuiPageLayout"> <layout class="QVBoxLayout" name="tabWebUIPageLayout">
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
</property> </property>
@ -3253,7 +3253,7 @@ Disable encryption: Only connect to peers without protocol encryption</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_23"> <layout class="QVBoxLayout" name="verticalLayout_23">
<item> <item>
<widget class="QGroupBox" name="checkWebUi"> <widget class="QGroupBox" name="checkWebUI">
<property name="title"> <property name="title">
<string>Web User Interface (Remote control)</string> <string>Web User Interface (Remote control)</string>
</property> </property>
@ -3264,17 +3264,29 @@ Disable encryption: Only connect to peers without protocol encryption</string>
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="labelWebUIError">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QLabel" name="lblWebUiAddress"> <widget class="QLabel" name="lblWebUIAddress">
<property name="text"> <property name="text">
<string>IP address:</string> <string>IP address:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="textWebUiAddress"> <widget class="QLineEdit" name="textWebUIAddress">
<property name="toolTip"> <property name="toolTip">
<string>IP address that the Web UI will bind to. <string>IP address that the Web UI will bind to.
Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv4 address, Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv4 address,
@ -3283,14 +3295,14 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="lblWebUiPort"> <widget class="QLabel" name="lblWebUIPort">
<property name="text"> <property name="text">
<string>Port:</string> <string>Port:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="spinWebUiPort"> <widget class="QSpinBox" name="spinWebUIPort">
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
</property> </property>
@ -3315,7 +3327,7 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="checkWebUiHttps"> <widget class="QGroupBox" name="checkWebUIHttps">
<property name="title"> <property name="title">
<string>&amp;Use HTTPS instead of HTTP</string> <string>&amp;Use HTTPS instead of HTTP</string>
</property> </property>
@ -3327,14 +3339,14 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</property> </property>
<layout class="QGridLayout" name="gridLayout_11"> <layout class="QGridLayout" name="gridLayout_11">
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="lblWebUiKey"> <widget class="QLabel" name="lblWebUIKey">
<property name="text"> <property name="text">
<string>Key:</string> <string>Key:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="lblWebUiCrt"> <widget class="QLabel" name="lblWebUICrt">
<property name="text"> <property name="text">
<string>Certificate:</string> <string>Certificate:</string>
</property> </property>
@ -3366,7 +3378,7 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupWebUiAuth"> <widget class="QGroupBox" name="groupWebUIAuth">
<property name="title"> <property name="title">
<string>Authentication</string> <string>Authentication</string>
</property> </property>
@ -3374,24 +3386,24 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
<item> <item>
<layout class="QGridLayout" name="gridLayout_8"> <layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="lblWebUiUsername"> <widget class="QLabel" name="lblWebUIUsername">
<property name="text"> <property name="text">
<string>Username:</string> <string>Username:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="textWebUiUsername"/> <widget class="QLineEdit" name="textWebUIUsername"/>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="lblWebUiPassword"> <widget class="QLabel" name="lblWebUIPassword">
<property name="text"> <property name="text">
<string>Password:</string> <string>Password:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="textWebUiPassword"> <widget class="QLineEdit" name="textWebUIPassword">
<property name="echoMode"> <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::Password</enum>
</property> </property>
@ -3819,13 +3831,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
<tabstop>stopConditionComboBox</tabstop> <tabstop>stopConditionComboBox</tabstop>
<tabstop>spinPort</tabstop> <tabstop>spinPort</tabstop>
<tabstop>checkUPnP</tabstop> <tabstop>checkUPnP</tabstop>
<tabstop>textWebUiUsername</tabstop> <tabstop>textWebUIUsername</tabstop>
<tabstop>checkWebUi</tabstop> <tabstop>checkWebUI</tabstop>
<tabstop>textSavePath</tabstop> <tabstop>textSavePath</tabstop>
<tabstop>scrollArea_7</tabstop> <tabstop>scrollArea_7</tabstop>
<tabstop>scrollArea_2</tabstop> <tabstop>scrollArea_2</tabstop>
<tabstop>spinWebUiPort</tabstop> <tabstop>spinWebUIPort</tabstop>
<tabstop>textWebUiPassword</tabstop> <tabstop>textWebUIPassword</tabstop>
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
<tabstop>tabSelection</tabstop> <tabstop>tabSelection</tabstop>
<tabstop>scrollArea</tabstop> <tabstop>scrollArea</tabstop>
@ -3915,7 +3927,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
<tabstop>spinMaxActiveUploads</tabstop> <tabstop>spinMaxActiveUploads</tabstop>
<tabstop>spinMaxActiveTorrents</tabstop> <tabstop>spinMaxActiveTorrents</tabstop>
<tabstop>checkWebUIUPnP</tabstop> <tabstop>checkWebUIUPnP</tabstop>
<tabstop>checkWebUiHttps</tabstop> <tabstop>checkWebUIHttps</tabstop>
<tabstop>checkBypassLocalAuth</tabstop> <tabstop>checkBypassLocalAuth</tabstop>
<tabstop>checkBypassAuthSubnetWhitelist</tabstop> <tabstop>checkBypassAuthSubnetWhitelist</tabstop>
<tabstop>IPSubnetWhitelistButton</tabstop> <tabstop>IPSubnetWhitelistButton</tabstop>

View file

@ -92,7 +92,7 @@ void AppController::buildInfoAction()
void AppController::shutdownAction() void AppController::shutdownAction()
{ {
// Special handling for shutdown, we // Special handling for shutdown, we
// need to reply to the Web UI before // need to reply to the WebUI before
// actually shutting down. // actually shutting down.
QTimer::singleShot(100ms, Qt::CoarseTimer, qApp, [] QTimer::singleShot(100ms, Qt::CoarseTimer, qApp, []
{ {
@ -275,33 +275,33 @@ void AppController::preferencesAction()
data[u"add_trackers_enabled"_s] = session->isAddTrackersEnabled(); data[u"add_trackers_enabled"_s] = session->isAddTrackersEnabled();
data[u"add_trackers"_s] = session->additionalTrackers(); data[u"add_trackers"_s] = session->additionalTrackers();
// Web UI // WebUI
// HTTP Server // HTTP Server
data[u"web_ui_domain_list"_s] = pref->getServerDomains(); data[u"web_ui_domain_list"_s] = pref->getServerDomains();
data[u"web_ui_address"_s] = pref->getWebUiAddress(); data[u"web_ui_address"_s] = pref->getWebUIAddress();
data[u"web_ui_port"_s] = pref->getWebUiPort(); data[u"web_ui_port"_s] = pref->getWebUIPort();
data[u"web_ui_upnp"_s] = pref->useUPnPForWebUIPort(); data[u"web_ui_upnp"_s] = pref->useUPnPForWebUIPort();
data[u"use_https"_s] = pref->isWebUiHttpsEnabled(); data[u"use_https"_s] = pref->isWebUIHttpsEnabled();
data[u"web_ui_https_cert_path"_s] = pref->getWebUIHttpsCertificatePath().toString(); data[u"web_ui_https_cert_path"_s] = pref->getWebUIHttpsCertificatePath().toString();
data[u"web_ui_https_key_path"_s] = pref->getWebUIHttpsKeyPath().toString(); data[u"web_ui_https_key_path"_s] = pref->getWebUIHttpsKeyPath().toString();
// Authentication // Authentication
data[u"web_ui_username"_s] = pref->getWebUiUsername(); data[u"web_ui_username"_s] = pref->getWebUIUsername();
data[u"bypass_local_auth"_s] = !pref->isWebUiLocalAuthEnabled(); data[u"bypass_local_auth"_s] = !pref->isWebUILocalAuthEnabled();
data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUiAuthSubnetWhitelistEnabled(); data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUIAuthSubnetWhitelistEnabled();
QStringList authSubnetWhitelistStringList; QStringList authSubnetWhitelistStringList;
for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUiAuthSubnetWhitelist())) for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUIAuthSubnetWhitelist()))
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet); authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
data[u"bypass_auth_subnet_whitelist"_s] = authSubnetWhitelistStringList.join(u'\n'); data[u"bypass_auth_subnet_whitelist"_s] = authSubnetWhitelistStringList.join(u'\n');
data[u"web_ui_max_auth_fail_count"_s] = pref->getWebUIMaxAuthFailCount(); data[u"web_ui_max_auth_fail_count"_s] = pref->getWebUIMaxAuthFailCount();
data[u"web_ui_ban_duration"_s] = static_cast<int>(pref->getWebUIBanDuration().count()); data[u"web_ui_ban_duration"_s] = static_cast<int>(pref->getWebUIBanDuration().count());
data[u"web_ui_session_timeout"_s] = pref->getWebUISessionTimeout(); data[u"web_ui_session_timeout"_s] = pref->getWebUISessionTimeout();
// Use alternative Web UI // Use alternative WebUI
data[u"alternative_webui_enabled"_s] = pref->isAltWebUiEnabled(); data[u"alternative_webui_enabled"_s] = pref->isAltWebUIEnabled();
data[u"alternative_webui_path"_s] = pref->getWebUiRootFolder().toString(); data[u"alternative_webui_path"_s] = pref->getWebUIRootFolder().toString();
// Security // Security
data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUiClickjackingProtectionEnabled(); data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUIClickjackingProtectionEnabled();
data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUiCSRFProtectionEnabled(); data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUICSRFProtectionEnabled();
data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUiSecureCookieEnabled(); data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUISecureCookieEnabled();
data[u"web_ui_host_header_validation_enabled"_s] = pref->isWebUIHostHeaderValidationEnabled(); data[u"web_ui_host_header_validation_enabled"_s] = pref->isWebUIHostHeaderValidationEnabled();
// Custom HTTP headers // Custom HTTP headers
data[u"web_ui_use_custom_http_headers_enabled"_s] = pref->isWebUICustomHTTPHeadersEnabled(); data[u"web_ui_use_custom_http_headers_enabled"_s] = pref->isWebUICustomHTTPHeadersEnabled();
@ -782,35 +782,35 @@ void AppController::setPreferencesAction()
if (hasKey(u"add_trackers"_s)) if (hasKey(u"add_trackers"_s))
session->setAdditionalTrackers(it.value().toString()); session->setAdditionalTrackers(it.value().toString());
// Web UI // WebUI
// HTTP Server // HTTP Server
if (hasKey(u"web_ui_domain_list"_s)) if (hasKey(u"web_ui_domain_list"_s))
pref->setServerDomains(it.value().toString()); pref->setServerDomains(it.value().toString());
if (hasKey(u"web_ui_address"_s)) if (hasKey(u"web_ui_address"_s))
pref->setWebUiAddress(it.value().toString()); pref->setWebUIAddress(it.value().toString());
if (hasKey(u"web_ui_port"_s)) if (hasKey(u"web_ui_port"_s))
pref->setWebUiPort(it.value().value<quint16>()); pref->setWebUIPort(it.value().value<quint16>());
if (hasKey(u"web_ui_upnp"_s)) if (hasKey(u"web_ui_upnp"_s))
pref->setUPnPForWebUIPort(it.value().toBool()); pref->setUPnPForWebUIPort(it.value().toBool());
if (hasKey(u"use_https"_s)) if (hasKey(u"use_https"_s))
pref->setWebUiHttpsEnabled(it.value().toBool()); pref->setWebUIHttpsEnabled(it.value().toBool());
if (hasKey(u"web_ui_https_cert_path"_s)) if (hasKey(u"web_ui_https_cert_path"_s))
pref->setWebUIHttpsCertificatePath(Path(it.value().toString())); pref->setWebUIHttpsCertificatePath(Path(it.value().toString()));
if (hasKey(u"web_ui_https_key_path"_s)) if (hasKey(u"web_ui_https_key_path"_s))
pref->setWebUIHttpsKeyPath(Path(it.value().toString())); pref->setWebUIHttpsKeyPath(Path(it.value().toString()));
// Authentication // Authentication
if (hasKey(u"web_ui_username"_s)) if (hasKey(u"web_ui_username"_s))
pref->setWebUiUsername(it.value().toString()); pref->setWebUIUsername(it.value().toString());
if (hasKey(u"web_ui_password"_s)) if (hasKey(u"web_ui_password"_s))
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray())); pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray()));
if (hasKey(u"bypass_local_auth"_s)) if (hasKey(u"bypass_local_auth"_s))
pref->setWebUiLocalAuthEnabled(!it.value().toBool()); pref->setWebUILocalAuthEnabled(!it.value().toBool());
if (hasKey(u"bypass_auth_subnet_whitelist_enabled"_s)) if (hasKey(u"bypass_auth_subnet_whitelist_enabled"_s))
pref->setWebUiAuthSubnetWhitelistEnabled(it.value().toBool()); pref->setWebUIAuthSubnetWhitelistEnabled(it.value().toBool());
if (hasKey(u"bypass_auth_subnet_whitelist"_s)) if (hasKey(u"bypass_auth_subnet_whitelist"_s))
{ {
// recognize new lines and commas as delimiters // recognize new lines and commas as delimiters
pref->setWebUiAuthSubnetWhitelist(it.value().toString().split(QRegularExpression(u"\n|,"_s), Qt::SkipEmptyParts)); pref->setWebUIAuthSubnetWhitelist(it.value().toString().split(QRegularExpression(u"\n|,"_s), Qt::SkipEmptyParts));
} }
if (hasKey(u"web_ui_max_auth_fail_count"_s)) if (hasKey(u"web_ui_max_auth_fail_count"_s))
pref->setWebUIMaxAuthFailCount(it.value().toInt()); pref->setWebUIMaxAuthFailCount(it.value().toInt());
@ -818,18 +818,18 @@ void AppController::setPreferencesAction()
pref->setWebUIBanDuration(std::chrono::seconds {it.value().toInt()}); pref->setWebUIBanDuration(std::chrono::seconds {it.value().toInt()});
if (hasKey(u"web_ui_session_timeout"_s)) if (hasKey(u"web_ui_session_timeout"_s))
pref->setWebUISessionTimeout(it.value().toInt()); pref->setWebUISessionTimeout(it.value().toInt());
// Use alternative Web UI // Use alternative WebUI
if (hasKey(u"alternative_webui_enabled"_s)) if (hasKey(u"alternative_webui_enabled"_s))
pref->setAltWebUiEnabled(it.value().toBool()); pref->setAltWebUIEnabled(it.value().toBool());
if (hasKey(u"alternative_webui_path"_s)) if (hasKey(u"alternative_webui_path"_s))
pref->setWebUiRootFolder(Path(it.value().toString())); pref->setWebUIRootFolder(Path(it.value().toString()));
// Security // Security
if (hasKey(u"web_ui_clickjacking_protection_enabled"_s)) if (hasKey(u"web_ui_clickjacking_protection_enabled"_s))
pref->setWebUiClickjackingProtectionEnabled(it.value().toBool()); pref->setWebUIClickjackingProtectionEnabled(it.value().toBool());
if (hasKey(u"web_ui_csrf_protection_enabled"_s)) if (hasKey(u"web_ui_csrf_protection_enabled"_s))
pref->setWebUiCSRFProtectionEnabled(it.value().toBool()); pref->setWebUICSRFProtectionEnabled(it.value().toBool());
if (hasKey(u"web_ui_secure_cookie_enabled"_s)) if (hasKey(u"web_ui_secure_cookie_enabled"_s))
pref->setWebUiSecureCookieEnabled(it.value().toBool()); pref->setWebUISecureCookieEnabled(it.value().toBool());
if (hasKey(u"web_ui_host_header_validation_enabled"_s)) if (hasKey(u"web_ui_host_header_validation_enabled"_s))
pref->setWebUIHostHeaderValidationEnabled(it.value().toBool()); pref->setWebUIHostHeaderValidationEnabled(it.value().toBool());
// Custom HTTP headers // Custom HTTP headers

View file

@ -43,6 +43,16 @@ AuthController::AuthController(ISessionManager *sessionManager, IApplication *ap
{ {
} }
void AuthController::setUsername(const QString &username)
{
m_username = username;
}
void AuthController::setPasswordHash(const QByteArray &passwordHash)
{
m_passwordHash = passwordHash;
}
void AuthController::loginAction() void AuthController::loginAction()
{ {
if (m_sessionManager->session()) if (m_sessionManager->session())
@ -51,9 +61,9 @@ void AuthController::loginAction()
return; return;
} }
const QString clientAddr {m_sessionManager->clientId()}; const QString clientAddr = m_sessionManager->clientId();
const QString usernameFromWeb {params()[u"username"_s]}; const QString usernameFromWeb = params()[u"username"_s];
const QString passwordFromWeb {params()[u"password"_s]}; const QString passwordFromWeb = params()[u"password"_s];
if (isBanned()) if (isBanned())
{ {
@ -64,12 +74,8 @@ void AuthController::loginAction()
, tr("Your IP address has been banned after too many failed authentication attempts.")); , tr("Your IP address has been banned after too many failed authentication attempts."));
} }
const Preferences *pref = Preferences::instance(); const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), m_username.toUtf8());
const bool passwordEqual = Utils::Password::PBKDF2::verify(m_passwordHash, passwordFromWeb);
const QString username {pref->getWebUiUsername()};
const QByteArray secret {pref->getWebUIPassword()};
const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), username.toUtf8());
const bool passwordEqual = Utils::Password::PBKDF2::verify(secret, passwordFromWeb);
if (usernameEqual && passwordEqual) if (usernameEqual && passwordEqual)
{ {

View file

@ -28,8 +28,10 @@
#pragma once #pragma once
#include <QByteArray>
#include <QDeadlineTimer> #include <QDeadlineTimer>
#include <QHash> #include <QHash>
#include <QString>
#include "apicontroller.h" #include "apicontroller.h"
@ -45,6 +47,9 @@ class AuthController : public APIController
public: public:
explicit AuthController(ISessionManager *sessionManager, IApplication *app, QObject *parent = nullptr); explicit AuthController(ISessionManager *sessionManager, IApplication *app, QObject *parent = nullptr);
void setUsername(const QString &username);
void setPasswordHash(const QByteArray &passwordHash);
private slots: private slots:
void loginAction(); void loginAction();
void logoutAction() const; void logoutAction() const;
@ -56,6 +61,9 @@ private:
ISessionManager *m_sessionManager = nullptr; ISessionManager *m_sessionManager = nullptr;
QString m_username;
QByteArray m_passwordHash;
struct FailedLogin struct FailedLogin
{ {
int failedAttemptsCount = 0; int failedAttemptsCount = 0;

View file

@ -269,6 +269,16 @@ const Http::Environment &WebApplication::env() const
return m_env; return m_env;
} }
void WebApplication::setUsername(const QString &username)
{
m_authController->setUsername(username);
}
void WebApplication::setPasswordHash(const QByteArray &passwordHash)
{
m_authController->setPasswordHash(passwordHash);
}
void WebApplication::doProcessRequest() void WebApplication::doProcessRequest()
{ {
const QRegularExpressionMatch match = m_apiPathPattern.match(request().path); const QRegularExpressionMatch match = m_apiPathPattern.match(request().path);
@ -357,17 +367,17 @@ void WebApplication::configure()
{ {
const auto *pref = Preferences::instance(); const auto *pref = Preferences::instance();
const bool isAltUIUsed = pref->isAltWebUiEnabled(); const bool isAltUIUsed = pref->isAltWebUIEnabled();
const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUiRootFolder()); const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUIRootFolder());
if ((isAltUIUsed != m_isAltUIUsed) || (rootFolder != m_rootFolder)) if ((isAltUIUsed != m_isAltUIUsed) || (rootFolder != m_rootFolder))
{ {
m_isAltUIUsed = isAltUIUsed; m_isAltUIUsed = isAltUIUsed;
m_rootFolder = rootFolder; m_rootFolder = rootFolder;
m_translatedFiles.clear(); m_translatedFiles.clear();
if (!m_isAltUIUsed) if (!m_isAltUIUsed)
LogMsg(tr("Using built-in Web UI.")); LogMsg(tr("Using built-in WebUI."));
else else
LogMsg(tr("Using custom Web UI. Location: \"%1\".").arg(m_rootFolder.toString())); LogMsg(tr("Using custom WebUI. Location: \"%1\".").arg(m_rootFolder.toString()));
} }
const QString newLocale = pref->getLocale(); const QString newLocale = pref->getLocale();
@ -379,27 +389,27 @@ void WebApplication::configure()
m_translationFileLoaded = m_translator.load((m_rootFolder / Path(u"translations/webui_"_s) + newLocale).data()); m_translationFileLoaded = m_translator.load((m_rootFolder / Path(u"translations/webui_"_s) + newLocale).data());
if (m_translationFileLoaded) if (m_translationFileLoaded)
{ {
LogMsg(tr("Web UI translation for selected locale (%1) has been successfully loaded.") LogMsg(tr("WebUI translation for selected locale (%1) has been successfully loaded.")
.arg(newLocale)); .arg(newLocale));
} }
else else
{ {
LogMsg(tr("Couldn't load Web UI translation for selected locale (%1).").arg(newLocale), Log::WARNING); LogMsg(tr("Couldn't load WebUI translation for selected locale (%1).").arg(newLocale), Log::WARNING);
} }
} }
m_isLocalAuthEnabled = pref->isWebUiLocalAuthEnabled(); m_isLocalAuthEnabled = pref->isWebUILocalAuthEnabled();
m_isAuthSubnetWhitelistEnabled = pref->isWebUiAuthSubnetWhitelistEnabled(); m_isAuthSubnetWhitelistEnabled = pref->isWebUIAuthSubnetWhitelistEnabled();
m_authSubnetWhitelist = pref->getWebUiAuthSubnetWhitelist(); m_authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist();
m_sessionTimeout = pref->getWebUISessionTimeout(); m_sessionTimeout = pref->getWebUISessionTimeout();
m_domainList = pref->getServerDomains().split(u';', Qt::SkipEmptyParts); m_domainList = pref->getServerDomains().split(u';', Qt::SkipEmptyParts);
std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); }); std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); });
m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled(); m_isCSRFProtectionEnabled = pref->isWebUICSRFProtectionEnabled();
m_isSecureCookieEnabled = pref->isWebUiSecureCookieEnabled(); m_isSecureCookieEnabled = pref->isWebUISecureCookieEnabled();
m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled(); m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled();
m_isHttpsEnabled = pref->isWebUiHttpsEnabled(); m_isHttpsEnabled = pref->isWebUIHttpsEnabled();
m_prebuiltHeaders.clear(); m_prebuiltHeaders.clear();
m_prebuiltHeaders.push_back({Http::HEADER_X_XSS_PROTECTION, u"1; mode=block"_s}); m_prebuiltHeaders.push_back({Http::HEADER_X_XSS_PROTECTION, u"1; mode=block"_s});
@ -411,7 +421,7 @@ void WebApplication::configure()
m_prebuiltHeaders.push_back({Http::HEADER_REFERRER_POLICY, u"same-origin"_s}); m_prebuiltHeaders.push_back({Http::HEADER_REFERRER_POLICY, u"same-origin"_s});
} }
const bool isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled(); const bool isClickjackingProtectionEnabled = pref->isWebUIClickjackingProtectionEnabled();
if (isClickjackingProtectionEnabled) if (isClickjackingProtectionEnabled)
m_prebuiltHeaders.push_back({Http::HEADER_X_FRAME_OPTIONS, u"SAMEORIGIN"_s}); m_prebuiltHeaders.push_back({Http::HEADER_X_FRAME_OPTIONS, u"SAMEORIGIN"_s});

View file

@ -105,6 +105,9 @@ public:
const Http::Request &request() const; const Http::Request &request() const;
const Http::Environment &env() const; const Http::Environment &env() const;
void setUsername(const QString &username);
void setPasswordHash(const QByteArray &passwordHash);
private: private:
void doProcessRequest(); void doProcessRequest();
void configure(); void configure();

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2023 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -28,6 +28,7 @@
#include "webui.h" #include "webui.h"
#include "base/global.h"
#include "base/http/server.h" #include "base/http/server.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/net/dnsupdater.h" #include "base/net/dnsupdater.h"
@ -36,10 +37,12 @@
#include "base/preferences.h" #include "base/preferences.h"
#include "base/utils/io.h" #include "base/utils/io.h"
#include "base/utils/net.h" #include "base/utils/net.h"
#include "base/utils/password.h"
#include "webapplication.h" #include "webapplication.h"
WebUI::WebUI(IApplication *app) WebUI::WebUI(IApplication *app, const QByteArray &tempPasswordHash)
: ApplicationComponent(app) : ApplicationComponent(app)
, m_passwordHash {tempPasswordHash}
{ {
configure(); configure();
connect(Preferences::instance(), &Preferences::changed, this, &WebUI::configure); connect(Preferences::instance(), &Preferences::changed, this, &WebUI::configure);
@ -49,12 +52,23 @@ void WebUI::configure()
{ {
m_isErrored = false; // clear previous error state m_isErrored = false; // clear previous error state
const QString portForwardingProfile = u"webui"_s;
const Preferences *pref = Preferences::instance(); const Preferences *pref = Preferences::instance();
const quint16 port = pref->getWebUiPort(); const bool isEnabled = pref->isWebUIEnabled();
const QString username = pref->getWebUIUsername();
if (const QByteArray passwordHash = pref->getWebUIPassword(); !passwordHash.isEmpty())
m_passwordHash = passwordHash;
if (pref->isWebUiEnabled()) if (isEnabled && (username.isEmpty() || m_passwordHash.isEmpty()))
{ {
setError(tr("Credentials are not set"));
}
const QString portForwardingProfile = u"webui"_s;
if (isEnabled && !m_isErrored)
{
const quint16 port = pref->getWebUIPort();
// Port forwarding // Port forwarding
auto *portForwarder = Net::PortForwarder::instance(); auto *portForwarder = Net::PortForwarder::instance();
if (pref->useUPnPForWebUIPort()) if (pref->useUPnPForWebUIPort())
@ -67,7 +81,7 @@ void WebUI::configure()
} }
// http server // http server
const QString serverAddressString = pref->getWebUiAddress(); const QString serverAddressString = pref->getWebUIAddress();
const auto serverAddress = ((serverAddressString == u"*") || serverAddressString.isEmpty()) const auto serverAddress = ((serverAddressString == u"*") || serverAddressString.isEmpty())
? QHostAddress::Any : QHostAddress(serverAddressString); ? QHostAddress::Any : QHostAddress(serverAddressString);
@ -82,7 +96,10 @@ void WebUI::configure()
m_httpServer->close(); m_httpServer->close();
} }
if (pref->isWebUiHttpsEnabled()) m_webapp->setUsername(username);
m_webapp->setPasswordHash(m_passwordHash);
if (pref->isWebUIHttpsEnabled())
{ {
const auto readData = [](const Path &path) -> QByteArray const auto readData = [](const Path &path) -> QByteArray
{ {
@ -94,9 +111,9 @@ void WebUI::configure()
const bool success = m_httpServer->setupHttps(cert, key); const bool success = m_httpServer->setupHttps(cert, key);
if (success) if (success)
LogMsg(tr("Web UI: HTTPS setup successful")); LogMsg(tr("WebUI: HTTPS setup successful"));
else else
LogMsg(tr("Web UI: HTTPS setup failed, fallback to HTTP"), Log::CRITICAL); LogMsg(tr("WebUI: HTTPS setup failed, fallback to HTTP"), Log::CRITICAL);
} }
else else
{ {
@ -108,17 +125,12 @@ void WebUI::configure()
const bool success = m_httpServer->listen(serverAddress, port); const bool success = m_httpServer->listen(serverAddress, port);
if (success) if (success)
{ {
LogMsg(tr("Web UI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(port)); LogMsg(tr("WebUI: Now listening on IP: %1, port: %2").arg(serverAddressString).arg(port));
} }
else else
{ {
const QString errorMsg = tr("Web UI: Unable to bind to IP: %1, port: %2. Reason: %3") setError(tr("Unable to bind to IP: %1, port: %2. Reason: %3")
.arg(serverAddressString).arg(port).arg(m_httpServer->errorString()); .arg(serverAddressString).arg(port).arg(m_httpServer->errorString()));
LogMsg(errorMsg, Log::CRITICAL);
qCritical() << errorMsg;
m_isErrored = true;
emit fatalError();
} }
} }
@ -145,7 +157,24 @@ void WebUI::configure()
} }
} }
void WebUI::setError(const QString &message)
{
m_isErrored = true;
m_errorMsg = message;
const QString logMessage = u"WebUI: " + m_errorMsg;
LogMsg(logMessage, Log::CRITICAL);
qCritical() << logMessage;
emit fatalError();
}
bool WebUI::isErrored() const bool WebUI::isErrored() const
{ {
return m_isErrored; return m_isErrored;
} }
QString WebUI::errorMessage() const
{
return m_errorMsg;
}

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015, 2023 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -51,9 +51,10 @@ class WebUI : public QObject, public ApplicationComponent
Q_DISABLE_COPY_MOVE(WebUI) Q_DISABLE_COPY_MOVE(WebUI)
public: public:
explicit WebUI(IApplication *app); explicit WebUI(IApplication *app, const QByteArray &tempPasswordHash = {});
bool isErrored() const; bool isErrored() const;
QString errorMessage() const;
signals: signals:
void fatalError(); void fatalError();
@ -62,8 +63,13 @@ private slots:
void configure(); void configure();
private: private:
void setError(const QString &message);
bool m_isErrored = false; bool m_isErrored = false;
QString m_errorMsg;
QPointer<Http::Server> m_httpServer; QPointer<Http::Server> m_httpServer;
QPointer<Net::DNSUpdater> m_dnsUpdater; QPointer<Net::DNSUpdater> m_dnsUpdater;
QPointer<WebApplication> m_webapp; QPointer<WebApplication> m_webapp;
QByteArray m_passwordHash;
}; };