mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-12-27 18:28:18 +03:00
parent
2c2252d7d9
commit
786c09e981
21 changed files with 378 additions and 254 deletions
|
@ -96,7 +96,6 @@
|
|||
#include "gui/mainwindow.h"
|
||||
#include "gui/shutdownconfirmdialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "gui/windowstate.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -106,6 +105,9 @@
|
|||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "webui/webui.h"
|
||||
#ifdef DISABLE_GUI
|
||||
#include "base/utils/password.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
|
@ -310,8 +312,8 @@ Application::Application(int &argc, char **argv)
|
|||
if (isFileLoggerEnabled())
|
||||
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
|
||||
Preferences::instance()->setWebUiPort(m_commandLineArgs.webUiPort);
|
||||
if (m_commandLineArgs.webUIPort > 0) // it will be -1 when user did not set any value
|
||||
Preferences::instance()->setWebUIPort(m_commandLineArgs.webUIPort);
|
||||
|
||||
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
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#ifndef DISABLE_GUI
|
||||
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())
|
||||
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"))
|
||||
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
|
||||
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'
|
||||
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_s) + u'\n'
|
||||
+ tr("This is a security risk, please change your password in program preferences.") + u'\n';
|
||||
const QString warning = tr("The WebUI administrator username is: %1").arg(pref->getWebUIUsername()) + u'\n'
|
||||
+ tr("The WebUI administrator password was not set. A temporary password is provided for this session: %1").arg(tempPassword) + u'\n'
|
||||
+ tr("You should set your own password in program preferences.") + u'\n';
|
||||
printf("%s", qUtf8Printable(warning));
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
@ -1300,3 +1305,10 @@ void Application::cleanup()
|
|||
Utils::Misc::shutdownComputer(m_shutdownAct);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
WebUI *Application::webUI() const
|
||||
{
|
||||
return m_webui;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -149,6 +149,10 @@ private slots:
|
|||
#endif
|
||||
|
||||
private:
|
||||
#ifndef DISABLE_WEBUI
|
||||
WebUI *webUI() const override;
|
||||
#endif
|
||||
|
||||
void initializeTranslation();
|
||||
void processParams(const QBtCommandLineParameters ¶ms);
|
||||
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
|
||||
|
|
|
@ -349,7 +349,7 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
|
|||
#elif !defined(Q_OS_WIN)
|
||||
, shouldDaemonize(DAEMON_OPTION.value(env))
|
||||
#endif
|
||||
, webUiPort(WEBUI_PORT_OPTION.value(env, -1))
|
||||
, webUIPort(WEBUI_PORT_OPTION.value(env, -1))
|
||||
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
|
||||
, skipDialog(SKIP_DIALOG_OPTION.value(env))
|
||||
, profileDir(PROFILE_OPTION.value(env))
|
||||
|
@ -387,8 +387,8 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
|||
#endif
|
||||
else if (arg == WEBUI_PORT_OPTION)
|
||||
{
|
||||
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
|
||||
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
|
||||
result.webUIPort = WEBUI_PORT_OPTION.value(arg);
|
||||
if ((result.webUIPort < 1) || (result.webUIPort > 65535))
|
||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
|
||||
.arg(u"--webui-port"_s));
|
||||
}
|
||||
|
@ -509,7 +509,7 @@ QString makeUsage(const QString &prgName)
|
|||
#endif
|
||||
+ 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"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the WebUI port"))
|
||||
+ u'\n'
|
||||
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
|
||||
|
|
|
@ -53,7 +53,7 @@ struct QBtCommandLineParameters
|
|||
#elif !defined(Q_OS_WIN)
|
||||
bool shouldDaemonize = false;
|
||||
#endif
|
||||
int webUiPort = -1;
|
||||
int webUIPort = -1;
|
||||
int torrentingPort = -1;
|
||||
std::optional<bool> skipDialog;
|
||||
Path profileDir;
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
class QString;
|
||||
|
||||
class Path;
|
||||
class WebUI;
|
||||
struct QBtCommandLineParameters;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -83,4 +84,8 @@ public:
|
|||
virtual MemoryPriority processMemoryPriority() const = 0;
|
||||
virtual void setProcessMemoryPriority(MemoryPriority priority) = 0;
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
virtual WebUI *webUI() const = 0;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -628,7 +628,7 @@ void Preferences::setSearchEnabled(const bool enabled)
|
|||
setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiEnabled() const
|
||||
bool Preferences::isWebUIEnabled() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
const bool defaultValue = true;
|
||||
|
@ -638,41 +638,41 @@ bool Preferences::isWebUiEnabled() const
|
|||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/Enabled"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiLocalAuthEnabled() const
|
||||
bool Preferences::isWebUILocalAuthEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/LocalHostAuth"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiAuthSubnetWhitelistEnabled() const
|
||||
bool Preferences::isWebUIAuthSubnetWhitelistEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -689,7 +689,7 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
|||
return ret;
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
|
||||
void Preferences::setWebUIAuthSubnetWhitelist(QStringList subnets)
|
||||
{
|
||||
Algorithm::removeIf(subnets, [](const QString &subnet)
|
||||
{
|
||||
|
@ -712,27 +712,27 @@ void Preferences::setServerDomains(const QString &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();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAddress(const QString &addr)
|
||||
void Preferences::setWebUIAddress(const QString &addr)
|
||||
{
|
||||
if (addr == getWebUiAddress())
|
||||
if (addr == getWebUIAddress())
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiPort(const quint16 port)
|
||||
void Preferences::setWebUIPort(const quint16 port)
|
||||
{
|
||||
if (port == getWebUiPort())
|
||||
if (port == getWebUIPort())
|
||||
return;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
QString Preferences::getWebUiUsername() const
|
||||
QString Preferences::getWebUIUsername() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/Username"_s, username);
|
||||
|
@ -767,9 +767,7 @@ void Preferences::setWebUiUsername(const QString &username)
|
|||
|
||||
QByteArray Preferences::getWebUIPassword() const
|
||||
{
|
||||
// default: adminadmin
|
||||
const auto defaultValue = QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==");
|
||||
return value(u"Preferences/WebUI/Password_PBKDF2"_s, defaultValue);
|
||||
return value<QByteArray>(u"Preferences/WebUI/Password_PBKDF2"_s);
|
||||
}
|
||||
|
||||
void Preferences::setWebUIPassword(const QByteArray &password)
|
||||
|
@ -832,40 +830,40 @@ void Preferences::setWebAPISessionCookieName(const QString &cookieName)
|
|||
setValue(u"WebAPI/SessionCookieName"_s, cookieName);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiClickjackingProtectionEnabled() const
|
||||
bool Preferences::isWebUIClickjackingProtectionEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/ClickjackingProtection"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiCSRFProtectionEnabled() const
|
||||
bool Preferences::isWebUICSRFProtectionEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/CSRFProtection"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiSecureCookieEnabled() const
|
||||
bool Preferences::isWebUISecureCookieEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/SecureCookie"_s, enabled);
|
||||
|
@ -884,14 +882,14 @@ void Preferences::setWebUIHostHeaderValidationEnabled(const bool 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);
|
||||
}
|
||||
|
||||
void Preferences::setWebUiHttpsEnabled(const bool enabled)
|
||||
void Preferences::setWebUIHttpsEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isWebUiHttpsEnabled())
|
||||
if (enabled == isWebUIHttpsEnabled())
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool Preferences::isAltWebUiEnabled() const
|
||||
bool Preferences::isAltWebUIEnabled() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/AlternativeUIEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
Path Preferences::getWebUiRootFolder() const
|
||||
Path Preferences::getWebUIRootFolder() const
|
||||
{
|
||||
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;
|
||||
|
||||
setValue(u"Preferences/WebUI/RootFolder"_s, path);
|
||||
|
|
|
@ -169,26 +169,26 @@ public:
|
|||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// HTTP Server
|
||||
bool isWebUiEnabled() const;
|
||||
void setWebUiEnabled(bool enabled);
|
||||
bool isWebUIEnabled() const;
|
||||
void setWebUIEnabled(bool enabled);
|
||||
QString getServerDomains() const;
|
||||
void setServerDomains(const QString &str);
|
||||
QString getWebUiAddress() const;
|
||||
void setWebUiAddress(const QString &addr);
|
||||
quint16 getWebUiPort() const;
|
||||
void setWebUiPort(quint16 port);
|
||||
QString getWebUIAddress() const;
|
||||
void setWebUIAddress(const QString &addr);
|
||||
quint16 getWebUIPort() const;
|
||||
void setWebUIPort(quint16 port);
|
||||
bool useUPnPForWebUIPort() const;
|
||||
void setUPnPForWebUIPort(bool enabled);
|
||||
|
||||
// Authentication
|
||||
bool isWebUiLocalAuthEnabled() const;
|
||||
void setWebUiLocalAuthEnabled(bool enabled);
|
||||
bool isWebUiAuthSubnetWhitelistEnabled() const;
|
||||
void setWebUiAuthSubnetWhitelistEnabled(bool enabled);
|
||||
QVector<Utils::Net::Subnet> getWebUiAuthSubnetWhitelist() const;
|
||||
void setWebUiAuthSubnetWhitelist(QStringList subnets);
|
||||
QString getWebUiUsername() const;
|
||||
void setWebUiUsername(const QString &username);
|
||||
bool isWebUILocalAuthEnabled() const;
|
||||
void setWebUILocalAuthEnabled(bool enabled);
|
||||
bool isWebUIAuthSubnetWhitelistEnabled() const;
|
||||
void setWebUIAuthSubnetWhitelistEnabled(bool enabled);
|
||||
QVector<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
|
||||
void setWebUIAuthSubnetWhitelist(QStringList subnets);
|
||||
QString getWebUIUsername() const;
|
||||
void setWebUIUsername(const QString &username);
|
||||
QByteArray getWebUIPassword() const;
|
||||
void setWebUIPassword(const QByteArray &password);
|
||||
int getWebUIMaxAuthFailCount() const;
|
||||
|
@ -201,26 +201,26 @@ public:
|
|||
void setWebAPISessionCookieName(const QString &cookieName);
|
||||
|
||||
// WebUI security
|
||||
bool isWebUiClickjackingProtectionEnabled() const;
|
||||
void setWebUiClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUiCSRFProtectionEnabled() const;
|
||||
void setWebUiCSRFProtectionEnabled(bool enabled);
|
||||
bool isWebUiSecureCookieEnabled () const;
|
||||
void setWebUiSecureCookieEnabled(bool enabled);
|
||||
bool isWebUIClickjackingProtectionEnabled() const;
|
||||
void setWebUIClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUICSRFProtectionEnabled() const;
|
||||
void setWebUICSRFProtectionEnabled(bool enabled);
|
||||
bool isWebUISecureCookieEnabled () const;
|
||||
void setWebUISecureCookieEnabled(bool enabled);
|
||||
bool isWebUIHostHeaderValidationEnabled() const;
|
||||
void setWebUIHostHeaderValidationEnabled(bool enabled);
|
||||
|
||||
// HTTPS
|
||||
bool isWebUiHttpsEnabled() const;
|
||||
void setWebUiHttpsEnabled(bool enabled);
|
||||
bool isWebUIHttpsEnabled() const;
|
||||
void setWebUIHttpsEnabled(bool enabled);
|
||||
Path getWebUIHttpsCertificatePath() const;
|
||||
void setWebUIHttpsCertificatePath(const Path &path);
|
||||
Path getWebUIHttpsKeyPath() const;
|
||||
void setWebUIHttpsKeyPath(const Path &path);
|
||||
bool isAltWebUiEnabled() const;
|
||||
void setAltWebUiEnabled(bool enabled);
|
||||
Path getWebUiRootFolder() const;
|
||||
void setWebUiRootFolder(const Path &path);
|
||||
bool isAltWebUIEnabled() const;
|
||||
void setAltWebUIEnabled(bool enabled);
|
||||
Path getWebUIRootFolder() const;
|
||||
void setWebUIRootFolder(const Path &path);
|
||||
|
||||
// WebUI custom HTTP headers
|
||||
bool isWebUICustomHTTPHeadersEnabled() const;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -36,6 +37,7 @@
|
|||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "bytearray.h"
|
||||
#include "random.h"
|
||||
|
||||
|
@ -65,6 +67,21 @@ bool Utils::Password::slowEquals(const QByteArray &a, const QByteArray &b)
|
|||
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)
|
||||
{
|
||||
return generate(password.toUtf8());
|
||||
|
@ -72,9 +89,8 @@ QByteArray Utils::Password::PBKDF2::generate(const QString &password)
|
|||
|
||||
QByteArray Utils::Password::PBKDF2::generate(const QByteArray &password)
|
||||
{
|
||||
const std::array<uint32_t, 4> salt
|
||||
{{Random::rand(), Random::rand()
|
||||
, Random::rand(), Random::rand()}};
|
||||
const std::array<uint32_t, 4> salt {
|
||||
{Random::rand(), Random::rand(), Random::rand(), Random::rand()}};
|
||||
|
||||
std::array<unsigned char, 64> outBuf {};
|
||||
const int hmacResult = PKCS5_PBKDF2_HMAC(password.constData(), password.size()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* 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
|
||||
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
||||
|
||||
QString generate();
|
||||
|
||||
namespace PBKDF2
|
||||
{
|
||||
QByteArray generate(const QString &password);
|
||||
|
|
|
@ -50,7 +50,7 @@ IPSubnetWhitelistOptionsDialog::IPSubnetWhitelistOptionsDialog(QWidget *parent)
|
|||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
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);
|
||||
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
|
||||
for (int i = 0; i < m_sortFilter->rowCount(); ++i)
|
||||
subnets.append(m_sortFilter->index(i, 0).data().toString());
|
||||
Preferences::instance()->setWebUiAuthSubnetWhitelist(subnets);
|
||||
Preferences::instance()->setWebUIAuthSubnetWhitelist(subnets);
|
||||
QDialog::accept();
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1182,7 +1182,7 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
|||
if (!isVisible())
|
||||
show();
|
||||
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?"),
|
||||
QMessageBox::NoButton, this);
|
||||
QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
#include "utils.h"
|
||||
#include "watchedfolderoptionsdialog.h"
|
||||
#include "watchedfoldersmodel.h"
|
||||
#include "webui/webui.h"
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "base/net/dnsupdater.h"
|
||||
|
@ -80,6 +81,9 @@
|
|||
|
||||
#define SETTINGS_KEY(name) u"OptionsDialog/" name
|
||||
|
||||
const int WEBUI_MIN_USERNAME_LENGTH = 3;
|
||||
const int WEBUI_MIN_PASSWORD_LENGTH = 6;
|
||||
|
||||
namespace
|
||||
{
|
||||
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
|
||||
// type casts and that is why we declare required member pointer here instead.
|
||||
void (QComboBox::*qComboBoxCurrentIndexChanged)(int) = &QComboBox::currentIndexChanged;
|
||||
|
@ -175,7 +189,11 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent)
|
|||
|
||||
// setup apply button
|
||||
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
|
||||
auto *wheelEventEater = new WheelEventEater(this);
|
||||
|
@ -1211,28 +1229,33 @@ void OptionsDialog::loadWebUITabOptions()
|
|||
m_ui->textWebUIRootFolder->setMode(FileSystemPathEdit::Mode::DirectoryOpen);
|
||||
m_ui->textWebUIRootFolder->setDialogCaption(tr("Choose Alternative UI files location"));
|
||||
|
||||
m_ui->checkWebUi->setChecked(pref->isWebUiEnabled());
|
||||
m_ui->textWebUiAddress->setText(pref->getWebUiAddress());
|
||||
m_ui->spinWebUiPort->setValue(pref->getWebUiPort());
|
||||
if (app()->webUI()->isErrored())
|
||||
m_ui->labelWebUIError->setText(tr("WebUI configuration failed. Reason: %1").arg(app()->webUI()->errorMessage()));
|
||||
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->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled());
|
||||
m_ui->checkWebUIHttps->setChecked(pref->isWebUIHttpsEnabled());
|
||||
webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath());
|
||||
webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath());
|
||||
m_ui->textWebUiUsername->setText(pref->getWebUiUsername());
|
||||
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
|
||||
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
|
||||
m_ui->textWebUIUsername->setText(pref->getWebUIUsername());
|
||||
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->spinBanDuration->setValue(pref->getWebUIBanDuration().count());
|
||||
m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout());
|
||||
// Alternative UI
|
||||
m_ui->groupAltWebUI->setChecked(pref->isAltWebUiEnabled());
|
||||
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUiRootFolder());
|
||||
m_ui->groupAltWebUI->setChecked(pref->isAltWebUIEnabled());
|
||||
m_ui->textWebUIRootFolder->setSelectedPath(pref->getWebUIRootFolder());
|
||||
// Security
|
||||
m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled());
|
||||
m_ui->checkCSRFProtection->setChecked(pref->isWebUiCSRFProtectionEnabled());
|
||||
m_ui->checkSecureCookie->setEnabled(pref->isWebUiHttpsEnabled());
|
||||
m_ui->checkSecureCookie->setChecked(pref->isWebUiSecureCookieEnabled());
|
||||
m_ui->checkClickjacking->setChecked(pref->isWebUIClickjackingProtectionEnabled());
|
||||
m_ui->checkCSRFProtection->setChecked(pref->isWebUICSRFProtectionEnabled());
|
||||
m_ui->checkSecureCookie->setEnabled(pref->isWebUIHttpsEnabled());
|
||||
m_ui->checkSecureCookie->setChecked(pref->isWebUISecureCookieEnabled());
|
||||
m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
|
||||
m_ui->textServerDomains->setText(pref->getServerDomains());
|
||||
// Custom HTTP headers
|
||||
|
@ -1248,18 +1271,18 @@ void OptionsDialog::loadWebUITabOptions()
|
|||
m_ui->DNSUsernameTxt->setText(pref->getDynDNSUsername());
|
||||
m_ui->DNSPasswordTxt->setText(pref->getDynDNSPassword());
|
||||
|
||||
connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkWebUI, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->spinWebUIPort, qSpinBoxValueChanged, 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, &OptionsDialog::webUIHttpsCertChanged);
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &OptionsDialog::webUIHttpsKeyChanged);
|
||||
|
||||
connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUiPassword, &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->checkBypassLocalAuth, &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->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->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||
|
@ -1295,29 +1318,32 @@ void OptionsDialog::saveWebUITabOptions() const
|
|||
{
|
||||
auto *pref = Preferences::instance();
|
||||
|
||||
pref->setWebUiEnabled(isWebUiEnabled());
|
||||
pref->setWebUiAddress(m_ui->textWebUiAddress->text());
|
||||
pref->setWebUiPort(m_ui->spinWebUiPort->value());
|
||||
const bool webUIEnabled = isWebUIEnabled();
|
||||
|
||||
pref->setWebUIEnabled(webUIEnabled);
|
||||
pref->setWebUIAddress(m_ui->textWebUIAddress->text());
|
||||
pref->setWebUIPort(m_ui->spinWebUIPort->value());
|
||||
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->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
|
||||
pref->setWebUIMaxAuthFailCount(m_ui->spinBanCounter->value());
|
||||
pref->setWebUIBanDuration(std::chrono::seconds {m_ui->spinBanDuration->value()});
|
||||
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
|
||||
// Authentication
|
||||
pref->setWebUiUsername(webUiUsername());
|
||||
if (!webUiPassword().isEmpty())
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(webUiPassword()));
|
||||
pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
|
||||
pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
|
||||
if (const QString username = webUIUsername(); isValidWebUIUsername(username))
|
||||
pref->setWebUIUsername(username);
|
||||
if (const QString password = webUIPassword(); isValidWebUIPassword(password))
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(password));
|
||||
pref->setWebUILocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
|
||||
pref->setWebUIAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
|
||||
// Alternative UI
|
||||
pref->setAltWebUiEnabled(m_ui->groupAltWebUI->isChecked());
|
||||
pref->setWebUiRootFolder(m_ui->textWebUIRootFolder->selectedPath());
|
||||
pref->setAltWebUIEnabled(m_ui->groupAltWebUI->isChecked());
|
||||
pref->setWebUIRootFolder(m_ui->textWebUIRootFolder->selectedPath());
|
||||
// Security
|
||||
pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
|
||||
pref->setWebUiCSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
|
||||
pref->setWebUiSecureCookieEnabled(m_ui->checkSecureCookie->isChecked());
|
||||
pref->setWebUIClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
|
||||
pref->setWebUICSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
|
||||
pref->setWebUISecureCookieEnabled(m_ui->checkSecureCookie->isChecked());
|
||||
pref->setWebUIHostHeaderValidationEnabled(m_ui->groupHostHeaderValidation->isChecked());
|
||||
pref->setServerDomains(m_ui->textServerDomains->text());
|
||||
// Custom HTTP headers
|
||||
|
@ -1517,53 +1543,37 @@ void OptionsDialog::on_buttonBox_accepted()
|
|||
{
|
||||
if (m_applyButton->isEnabled())
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
if (!applySettings())
|
||||
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);
|
||||
saveOptions();
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void OptionsDialog::applySettings()
|
||||
bool OptionsDialog::applySettings()
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
#ifndef DISABLE_WEBUI
|
||||
if (!webUIAuthenticationOk())
|
||||
if (isWebUIEnabled() && !webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_applyButton->setEnabled(false);
|
||||
saveOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
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 true;
|
||||
|
@ -1893,7 +1905,7 @@ bool OptionsDialog::isAlternativeWebUIPathValid()
|
|||
{
|
||||
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 true;
|
||||
|
|
|
@ -88,7 +88,6 @@ private slots:
|
|||
void adjustProxyOptions();
|
||||
void on_buttonBox_accepted();
|
||||
void on_buttonBox_rejected();
|
||||
void applySettings();
|
||||
void enableApplyButton();
|
||||
void toggleComboRatioLimitAct();
|
||||
void changePage(QListWidgetItem *, QListWidgetItem *);
|
||||
|
@ -115,6 +114,7 @@ private:
|
|||
void showEvent(QShowEvent *e) override;
|
||||
|
||||
// Methods
|
||||
bool applySettings();
|
||||
void saveOptions() const;
|
||||
|
||||
void loadBehaviorTabOptions();
|
||||
|
@ -184,9 +184,9 @@ private:
|
|||
int getMaxActiveTorrents() const;
|
||||
// WebUI
|
||||
#ifndef DISABLE_WEBUI
|
||||
bool isWebUiEnabled() const;
|
||||
QString webUiUsername() const;
|
||||
QString webUiPassword() const;
|
||||
bool isWebUIEnabled() const;
|
||||
QString webUIUsername() const;
|
||||
QString webUIPassword() const;
|
||||
bool webUIAuthenticationOk();
|
||||
bool isAlternativeWebUIPathValid();
|
||||
#endif
|
||||
|
|
|
@ -3223,8 +3223,8 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabWebuiPage">
|
||||
<layout class="QVBoxLayout" name="tabWebuiPageLayout">
|
||||
<widget class="QWidget" name="tabWebUIPage">
|
||||
<layout class="QVBoxLayout" name="tabWebUIPageLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -3253,7 +3253,7 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_23">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkWebUi">
|
||||
<widget class="QGroupBox" name="checkWebUI">
|
||||
<property name="title">
|
||||
<string>Web User Interface (Remote control)</string>
|
||||
</property>
|
||||
|
@ -3264,17 +3264,29 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblWebUiAddress">
|
||||
<widget class="QLabel" name="lblWebUIAddress">
|
||||
<property name="text">
|
||||
<string>IP address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textWebUiAddress">
|
||||
<widget class="QLineEdit" name="textWebUIAddress">
|
||||
<property name="toolTip">
|
||||
<string>IP address that the Web UI will bind to.
|
||||
Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv4 address,
|
||||
|
@ -3283,14 +3295,14 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblWebUiPort">
|
||||
<widget class="QLabel" name="lblWebUIPort">
|
||||
<property name="text">
|
||||
<string>Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinWebUiPort">
|
||||
<widget class="QSpinBox" name="spinWebUIPort">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
|
@ -3315,7 +3327,7 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkWebUiHttps">
|
||||
<widget class="QGroupBox" name="checkWebUIHttps">
|
||||
<property name="title">
|
||||
<string>&Use HTTPS instead of HTTP</string>
|
||||
</property>
|
||||
|
@ -3327,14 +3339,14 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblWebUiKey">
|
||||
<widget class="QLabel" name="lblWebUIKey">
|
||||
<property name="text">
|
||||
<string>Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="lblWebUiCrt">
|
||||
<widget class="QLabel" name="lblWebUICrt">
|
||||
<property name="text">
|
||||
<string>Certificate:</string>
|
||||
</property>
|
||||
|
@ -3366,7 +3378,7 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupWebUiAuth">
|
||||
<widget class="QGroupBox" name="groupWebUIAuth">
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
|
@ -3374,24 +3386,24 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
|||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblWebUiUsername">
|
||||
<widget class="QLabel" name="lblWebUIUsername">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="textWebUiUsername"/>
|
||||
<widget class="QLineEdit" name="textWebUIUsername"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblWebUiPassword">
|
||||
<widget class="QLabel" name="lblWebUIPassword">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="textWebUiPassword">
|
||||
<widget class="QLineEdit" name="textWebUIPassword">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
|
@ -3819,13 +3831,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||
<tabstop>stopConditionComboBox</tabstop>
|
||||
<tabstop>spinPort</tabstop>
|
||||
<tabstop>checkUPnP</tabstop>
|
||||
<tabstop>textWebUiUsername</tabstop>
|
||||
<tabstop>checkWebUi</tabstop>
|
||||
<tabstop>textWebUIUsername</tabstop>
|
||||
<tabstop>checkWebUI</tabstop>
|
||||
<tabstop>textSavePath</tabstop>
|
||||
<tabstop>scrollArea_7</tabstop>
|
||||
<tabstop>scrollArea_2</tabstop>
|
||||
<tabstop>spinWebUiPort</tabstop>
|
||||
<tabstop>textWebUiPassword</tabstop>
|
||||
<tabstop>spinWebUIPort</tabstop>
|
||||
<tabstop>textWebUIPassword</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>tabSelection</tabstop>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
|
@ -3915,7 +3927,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||
<tabstop>spinMaxActiveUploads</tabstop>
|
||||
<tabstop>spinMaxActiveTorrents</tabstop>
|
||||
<tabstop>checkWebUIUPnP</tabstop>
|
||||
<tabstop>checkWebUiHttps</tabstop>
|
||||
<tabstop>checkWebUIHttps</tabstop>
|
||||
<tabstop>checkBypassLocalAuth</tabstop>
|
||||
<tabstop>checkBypassAuthSubnetWhitelist</tabstop>
|
||||
<tabstop>IPSubnetWhitelistButton</tabstop>
|
||||
|
|
|
@ -92,7 +92,7 @@ void AppController::buildInfoAction()
|
|||
void AppController::shutdownAction()
|
||||
{
|
||||
// Special handling for shutdown, we
|
||||
// need to reply to the Web UI before
|
||||
// need to reply to the WebUI before
|
||||
// actually shutting down.
|
||||
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"_s] = session->additionalTrackers();
|
||||
|
||||
// Web UI
|
||||
// WebUI
|
||||
// HTTP Server
|
||||
data[u"web_ui_domain_list"_s] = pref->getServerDomains();
|
||||
data[u"web_ui_address"_s] = pref->getWebUiAddress();
|
||||
data[u"web_ui_port"_s] = pref->getWebUiPort();
|
||||
data[u"web_ui_address"_s] = pref->getWebUIAddress();
|
||||
data[u"web_ui_port"_s] = pref->getWebUIPort();
|
||||
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_key_path"_s] = pref->getWebUIHttpsKeyPath().toString();
|
||||
// Authentication
|
||||
data[u"web_ui_username"_s] = pref->getWebUiUsername();
|
||||
data[u"bypass_local_auth"_s] = !pref->isWebUiLocalAuthEnabled();
|
||||
data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUiAuthSubnetWhitelistEnabled();
|
||||
data[u"web_ui_username"_s] = pref->getWebUIUsername();
|
||||
data[u"bypass_local_auth"_s] = !pref->isWebUILocalAuthEnabled();
|
||||
data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUIAuthSubnetWhitelistEnabled();
|
||||
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);
|
||||
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_ban_duration"_s] = static_cast<int>(pref->getWebUIBanDuration().count());
|
||||
data[u"web_ui_session_timeout"_s] = pref->getWebUISessionTimeout();
|
||||
// Use alternative Web UI
|
||||
data[u"alternative_webui_enabled"_s] = pref->isAltWebUiEnabled();
|
||||
data[u"alternative_webui_path"_s] = pref->getWebUiRootFolder().toString();
|
||||
// Use alternative WebUI
|
||||
data[u"alternative_webui_enabled"_s] = pref->isAltWebUIEnabled();
|
||||
data[u"alternative_webui_path"_s] = pref->getWebUIRootFolder().toString();
|
||||
// Security
|
||||
data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUiClickjackingProtectionEnabled();
|
||||
data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUiCSRFProtectionEnabled();
|
||||
data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUiSecureCookieEnabled();
|
||||
data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUIClickjackingProtectionEnabled();
|
||||
data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUICSRFProtectionEnabled();
|
||||
data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUISecureCookieEnabled();
|
||||
data[u"web_ui_host_header_validation_enabled"_s] = pref->isWebUIHostHeaderValidationEnabled();
|
||||
// Custom HTTP headers
|
||||
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))
|
||||
session->setAdditionalTrackers(it.value().toString());
|
||||
|
||||
// Web UI
|
||||
// WebUI
|
||||
// HTTP Server
|
||||
if (hasKey(u"web_ui_domain_list"_s))
|
||||
pref->setServerDomains(it.value().toString());
|
||||
if (hasKey(u"web_ui_address"_s))
|
||||
pref->setWebUiAddress(it.value().toString());
|
||||
pref->setWebUIAddress(it.value().toString());
|
||||
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))
|
||||
pref->setUPnPForWebUIPort(it.value().toBool());
|
||||
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))
|
||||
pref->setWebUIHttpsCertificatePath(Path(it.value().toString()));
|
||||
if (hasKey(u"web_ui_https_key_path"_s))
|
||||
pref->setWebUIHttpsKeyPath(Path(it.value().toString()));
|
||||
// Authentication
|
||||
if (hasKey(u"web_ui_username"_s))
|
||||
pref->setWebUiUsername(it.value().toString());
|
||||
pref->setWebUIUsername(it.value().toString());
|
||||
if (hasKey(u"web_ui_password"_s))
|
||||
pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray()));
|
||||
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))
|
||||
pref->setWebUiAuthSubnetWhitelistEnabled(it.value().toBool());
|
||||
pref->setWebUIAuthSubnetWhitelistEnabled(it.value().toBool());
|
||||
if (hasKey(u"bypass_auth_subnet_whitelist"_s))
|
||||
{
|
||||
// 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))
|
||||
pref->setWebUIMaxAuthFailCount(it.value().toInt());
|
||||
|
@ -818,18 +818,18 @@ void AppController::setPreferencesAction()
|
|||
pref->setWebUIBanDuration(std::chrono::seconds {it.value().toInt()});
|
||||
if (hasKey(u"web_ui_session_timeout"_s))
|
||||
pref->setWebUISessionTimeout(it.value().toInt());
|
||||
// Use alternative Web UI
|
||||
// Use alternative WebUI
|
||||
if (hasKey(u"alternative_webui_enabled"_s))
|
||||
pref->setAltWebUiEnabled(it.value().toBool());
|
||||
pref->setAltWebUIEnabled(it.value().toBool());
|
||||
if (hasKey(u"alternative_webui_path"_s))
|
||||
pref->setWebUiRootFolder(Path(it.value().toString()));
|
||||
pref->setWebUIRootFolder(Path(it.value().toString()));
|
||||
// Security
|
||||
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))
|
||||
pref->setWebUiCSRFProtectionEnabled(it.value().toBool());
|
||||
pref->setWebUICSRFProtectionEnabled(it.value().toBool());
|
||||
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))
|
||||
pref->setWebUIHostHeaderValidationEnabled(it.value().toBool());
|
||||
// Custom HTTP headers
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
if (m_sessionManager->session())
|
||||
|
@ -51,9 +61,9 @@ void AuthController::loginAction()
|
|||
return;
|
||||
}
|
||||
|
||||
const QString clientAddr {m_sessionManager->clientId()};
|
||||
const QString usernameFromWeb {params()[u"username"_s]};
|
||||
const QString passwordFromWeb {params()[u"password"_s]};
|
||||
const QString clientAddr = m_sessionManager->clientId();
|
||||
const QString usernameFromWeb = params()[u"username"_s];
|
||||
const QString passwordFromWeb = params()[u"password"_s];
|
||||
|
||||
if (isBanned())
|
||||
{
|
||||
|
@ -61,15 +71,11 @@ void AuthController::loginAction()
|
|||
.arg(clientAddr, usernameFromWeb)
|
||||
, Log::WARNING);
|
||||
throw APIError(APIErrorType::AccessDenied
|
||||
, 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 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);
|
||||
const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), m_username.toUtf8());
|
||||
const bool passwordEqual = Utils::Password::PBKDF2::verify(m_passwordHash, passwordFromWeb);
|
||||
|
||||
if (usernameEqual && passwordEqual)
|
||||
{
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDeadlineTimer>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "apicontroller.h"
|
||||
|
||||
|
@ -45,6 +47,9 @@ class AuthController : public APIController
|
|||
public:
|
||||
explicit AuthController(ISessionManager *sessionManager, IApplication *app, QObject *parent = nullptr);
|
||||
|
||||
void setUsername(const QString &username);
|
||||
void setPasswordHash(const QByteArray &passwordHash);
|
||||
|
||||
private slots:
|
||||
void loginAction();
|
||||
void logoutAction() const;
|
||||
|
@ -56,6 +61,9 @@ private:
|
|||
|
||||
ISessionManager *m_sessionManager = nullptr;
|
||||
|
||||
QString m_username;
|
||||
QByteArray m_passwordHash;
|
||||
|
||||
struct FailedLogin
|
||||
{
|
||||
int failedAttemptsCount = 0;
|
||||
|
|
|
@ -269,6 +269,16 @@ const Http::Environment &WebApplication::env() const
|
|||
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()
|
||||
{
|
||||
const QRegularExpressionMatch match = m_apiPathPattern.match(request().path);
|
||||
|
@ -357,17 +367,17 @@ void WebApplication::configure()
|
|||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
const bool isAltUIUsed = pref->isAltWebUiEnabled();
|
||||
const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUiRootFolder());
|
||||
const bool isAltUIUsed = pref->isAltWebUIEnabled();
|
||||
const Path rootFolder = (!isAltUIUsed ? Path(WWW_FOLDER) : pref->getWebUIRootFolder());
|
||||
if ((isAltUIUsed != m_isAltUIUsed) || (rootFolder != m_rootFolder))
|
||||
{
|
||||
m_isAltUIUsed = isAltUIUsed;
|
||||
m_rootFolder = rootFolder;
|
||||
m_translatedFiles.clear();
|
||||
if (!m_isAltUIUsed)
|
||||
LogMsg(tr("Using built-in Web UI."));
|
||||
LogMsg(tr("Using built-in WebUI."));
|
||||
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();
|
||||
|
@ -379,27 +389,27 @@ void WebApplication::configure()
|
|||
m_translationFileLoaded = m_translator.load((m_rootFolder / Path(u"translations/webui_"_s) + newLocale).data());
|
||||
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));
|
||||
}
|
||||
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_isAuthSubnetWhitelistEnabled = pref->isWebUiAuthSubnetWhitelistEnabled();
|
||||
m_authSubnetWhitelist = pref->getWebUiAuthSubnetWhitelist();
|
||||
m_isLocalAuthEnabled = pref->isWebUILocalAuthEnabled();
|
||||
m_isAuthSubnetWhitelistEnabled = pref->isWebUIAuthSubnetWhitelistEnabled();
|
||||
m_authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist();
|
||||
m_sessionTimeout = pref->getWebUISessionTimeout();
|
||||
|
||||
m_domainList = pref->getServerDomains().split(u';', Qt::SkipEmptyParts);
|
||||
std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); });
|
||||
|
||||
m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled();
|
||||
m_isSecureCookieEnabled = pref->isWebUiSecureCookieEnabled();
|
||||
m_isCSRFProtectionEnabled = pref->isWebUICSRFProtectionEnabled();
|
||||
m_isSecureCookieEnabled = pref->isWebUISecureCookieEnabled();
|
||||
m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled();
|
||||
m_isHttpsEnabled = pref->isWebUiHttpsEnabled();
|
||||
m_isHttpsEnabled = pref->isWebUIHttpsEnabled();
|
||||
|
||||
m_prebuiltHeaders.clear();
|
||||
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});
|
||||
}
|
||||
|
||||
const bool isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled();
|
||||
const bool isClickjackingProtectionEnabled = pref->isWebUIClickjackingProtectionEnabled();
|
||||
if (isClickjackingProtectionEnabled)
|
||||
m_prebuiltHeaders.push_back({Http::HEADER_X_FRAME_OPTIONS, u"SAMEORIGIN"_s});
|
||||
|
||||
|
|
|
@ -105,6 +105,9 @@ public:
|
|||
const Http::Request &request() const;
|
||||
const Http::Environment &env() const;
|
||||
|
||||
void setUsername(const QString &username);
|
||||
void setPasswordHash(const QByteArray &passwordHash);
|
||||
|
||||
private:
|
||||
void doProcessRequest();
|
||||
void configure();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "webui.h"
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/dnsupdater.h"
|
||||
|
@ -36,10 +37,12 @@
|
|||
#include "base/preferences.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/password.h"
|
||||
#include "webapplication.h"
|
||||
|
||||
WebUI::WebUI(IApplication *app)
|
||||
WebUI::WebUI(IApplication *app, const QByteArray &tempPasswordHash)
|
||||
: ApplicationComponent(app)
|
||||
, m_passwordHash {tempPasswordHash}
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &WebUI::configure);
|
||||
|
@ -49,12 +52,23 @@ void WebUI::configure()
|
|||
{
|
||||
m_isErrored = false; // clear previous error state
|
||||
|
||||
const QString portForwardingProfile = u"webui"_s;
|
||||
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
|
||||
auto *portForwarder = Net::PortForwarder::instance();
|
||||
if (pref->useUPnPForWebUIPort())
|
||||
|
@ -67,7 +81,7 @@ void WebUI::configure()
|
|||
}
|
||||
|
||||
// http server
|
||||
const QString serverAddressString = pref->getWebUiAddress();
|
||||
const QString serverAddressString = pref->getWebUIAddress();
|
||||
const auto serverAddress = ((serverAddressString == u"*") || serverAddressString.isEmpty())
|
||||
? QHostAddress::Any : QHostAddress(serverAddressString);
|
||||
|
||||
|
@ -82,7 +96,10 @@ void WebUI::configure()
|
|||
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
|
||||
{
|
||||
|
@ -94,9 +111,9 @@ void WebUI::configure()
|
|||
|
||||
const bool success = m_httpServer->setupHttps(cert, key);
|
||||
if (success)
|
||||
LogMsg(tr("Web UI: HTTPS setup successful"));
|
||||
LogMsg(tr("WebUI: HTTPS setup successful"));
|
||||
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
|
||||
{
|
||||
|
@ -108,17 +125,12 @@ void WebUI::configure()
|
|||
const bool success = m_httpServer->listen(serverAddress, port);
|
||||
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
|
||||
{
|
||||
const QString errorMsg = tr("Web UI: Unable to bind to IP: %1, port: %2. Reason: %3")
|
||||
.arg(serverAddressString).arg(port).arg(m_httpServer->errorString());
|
||||
LogMsg(errorMsg, Log::CRITICAL);
|
||||
qCritical() << errorMsg;
|
||||
|
||||
m_isErrored = true;
|
||||
emit fatalError();
|
||||
setError(tr("Unable to bind to IP: %1, port: %2. Reason: %3")
|
||||
.arg(serverAddressString).arg(port).arg(m_httpServer->errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
return m_isErrored;
|
||||
}
|
||||
|
||||
QString WebUI::errorMessage() const
|
||||
{
|
||||
return m_errorMsg;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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
|
||||
* 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)
|
||||
|
||||
public:
|
||||
explicit WebUI(IApplication *app);
|
||||
explicit WebUI(IApplication *app, const QByteArray &tempPasswordHash = {});
|
||||
|
||||
bool isErrored() const;
|
||||
QString errorMessage() const;
|
||||
|
||||
signals:
|
||||
void fatalError();
|
||||
|
@ -62,8 +63,13 @@ private slots:
|
|||
void configure();
|
||||
|
||||
private:
|
||||
void setError(const QString &message);
|
||||
|
||||
bool m_isErrored = false;
|
||||
QString m_errorMsg;
|
||||
QPointer<Http::Server> m_httpServer;
|
||||
QPointer<Net::DNSUpdater> m_dnsUpdater;
|
||||
QPointer<WebApplication> m_webapp;
|
||||
|
||||
QByteArray m_passwordHash;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue