mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-23 00:47:21 -06:00
Load WebUI certificate & key from file path
This allow users to update certificate & key more easily, i.e. without the need to import them into qbt. Closes #6675, #7547, #8315, #8564.
This commit is contained in:
@@ -42,13 +42,9 @@
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTranslator>
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/net/dnsupdater.h"
|
||||
#include "base/net/portforwarder.h"
|
||||
#include "base/net/proxyconfigurationmanager.h"
|
||||
@@ -59,6 +55,7 @@
|
||||
#include "base/torrentfileguard.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/password.h"
|
||||
#include "base/utils/random.h"
|
||||
#include "addnewtorrentdialog.h"
|
||||
@@ -196,11 +193,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(QT_NO_OPENSSL)
|
||||
m_ui->checkWebUiHttps->setVisible(false);
|
||||
m_ui->checkSmtpSSL->setVisible(false);
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
m_ui->checkStartup->setVisible(false);
|
||||
#endif
|
||||
@@ -390,14 +382,23 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
// Web UI tab
|
||||
m_ui->textWebUIHttpsCert->setMode(FileSystemPathEdit::Mode::FileOpen);
|
||||
m_ui->textWebUIHttpsCert->setFileNameFilter(tr("Certificate") + QLatin1String(" (*.cer *.crt *.pem)"));
|
||||
m_ui->textWebUIHttpsCert->setDialogCaption(tr("Select certificate"));
|
||||
m_ui->textWebUIHttpsKey->setMode(FileSystemPathEdit::Mode::FileOpen);
|
||||
m_ui->textWebUIHttpsKey->setFileNameFilter(tr("Private key") + QLatin1String(" (*.key *.pem)"));
|
||||
m_ui->textWebUIHttpsKey->setDialogCaption(tr("Select private key"));
|
||||
|
||||
connect(m_ui->textServerDomains, &QLineEdit::textChanged, 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->btnWebUiKey, &QAbstractButton::clicked, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->btnWebUiCrt, &QAbstractButton::clicked, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsCertChanged(s, ShowError::Show); });
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsKeyChanged(s, ShowError::Show); });
|
||||
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);
|
||||
@@ -722,11 +723,8 @@ void OptionsDialog::saveOptions()
|
||||
pref->setWebUiPort(m_ui->spinWebUiPort->value());
|
||||
pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked());
|
||||
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked());
|
||||
// HTTPS
|
||||
if (m_ui->checkWebUiHttps->isChecked()) {
|
||||
pref->setWebUiHttpsCertificate(m_sslCert);
|
||||
pref->setWebUiHttpsKey(m_sslKey);
|
||||
}
|
||||
pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
|
||||
pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
|
||||
// Authentication
|
||||
pref->setWebUiUsername(webUiUsername());
|
||||
if (!webUiPassword().isEmpty())
|
||||
@@ -1089,8 +1087,8 @@ void OptionsDialog::loadOptions()
|
||||
m_ui->spinWebUiPort->setValue(pref->getWebUiPort());
|
||||
m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort());
|
||||
m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled());
|
||||
setSslCertificate(pref->getWebUiHttpsCertificate());
|
||||
setSslKey(pref->getWebUiHttpsKey());
|
||||
webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath(), ShowError::NotShow);
|
||||
webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath(), ShowError::NotShow);
|
||||
m_ui->textWebUiUsername->setText(pref->getWebUiUsername());
|
||||
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
|
||||
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
|
||||
@@ -1559,41 +1557,59 @@ QString OptionsDialog::webUiPassword() const
|
||||
return m_ui->textWebUiPassword->text();
|
||||
}
|
||||
|
||||
void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError showError)
|
||||
{
|
||||
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
||||
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
|
||||
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) {
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
|
||||
}
|
||||
|
||||
void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError showError)
|
||||
{
|
||||
m_ui->textWebUIHttpsKey->setSelectedPath(path);
|
||||
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
|
||||
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) {
|
||||
if (showError == ShowError::Show)
|
||||
QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
|
||||
}
|
||||
|
||||
void OptionsDialog::showConnectionTab()
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_CONNECTION);
|
||||
}
|
||||
|
||||
void OptionsDialog::on_btnWebUiCrt_clicked()
|
||||
{
|
||||
const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL certificate"), QString(), tr("SSL Certificate") + QLatin1String(" (*.crt *.pem)"));
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
QFile cert(filename);
|
||||
if (!cert.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
|
||||
bool success = setSslCertificate(cert.read(1024 * 1024));
|
||||
if (!success)
|
||||
QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate."));
|
||||
}
|
||||
|
||||
void OptionsDialog::on_btnWebUiKey_clicked()
|
||||
{
|
||||
const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL key"), QString(), tr("SSL key") + QLatin1String(" (*.key *.pem)"));
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
QFile key(filename);
|
||||
if (!key.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
|
||||
bool success = setSslKey(key.read(1024 * 1024));
|
||||
if (!success)
|
||||
QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key."));
|
||||
}
|
||||
|
||||
void OptionsDialog::on_registerDNSBtn_clicked()
|
||||
{
|
||||
QDesktopServices::openUrl(Net::DNSUpdater::getRegistrationUrl(m_ui->comboDNSService->currentIndex()));
|
||||
@@ -1698,45 +1714,6 @@ QString OptionsDialog::languageToLocalizedString(const QLocale &locale)
|
||||
}
|
||||
}
|
||||
|
||||
bool OptionsDialog::setSslKey(const QByteArray &key)
|
||||
{
|
||||
#ifndef QT_NO_OPENSSL
|
||||
// try different formats
|
||||
const bool isKeyValid = (!QSslKey(key, QSsl::Rsa).isNull() || !QSslKey(key, QSsl::Ec).isNull());
|
||||
if (isKeyValid) {
|
||||
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
|
||||
m_sslKey = key;
|
||||
}
|
||||
else {
|
||||
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
|
||||
m_sslKey.clear();
|
||||
}
|
||||
return isKeyValid;
|
||||
#else
|
||||
Q_UNUSED(key);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OptionsDialog::setSslCertificate(const QByteArray &cert)
|
||||
{
|
||||
#ifndef QT_NO_OPENSSL
|
||||
const bool isCertValid = !QSslCertificate(cert).isNull();
|
||||
if (isCertValid) {
|
||||
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
|
||||
m_sslCert = cert;
|
||||
}
|
||||
else {
|
||||
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
|
||||
m_sslCert.clear();
|
||||
}
|
||||
return isCertValid;
|
||||
#else
|
||||
Q_UNUSED(cert);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OptionsDialog::schedTimesOk()
|
||||
{
|
||||
if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time()) {
|
||||
|
||||
@@ -60,7 +60,6 @@ class OptionsDialog : public QDialog
|
||||
Q_OBJECT
|
||||
using ThisType = OptionsDialog;
|
||||
|
||||
private:
|
||||
enum Tabs
|
||||
{
|
||||
TAB_UI,
|
||||
@@ -73,6 +72,12 @@ private:
|
||||
TAB_ADVANCED
|
||||
};
|
||||
|
||||
enum class ShowError
|
||||
{
|
||||
NotShow,
|
||||
Show
|
||||
};
|
||||
|
||||
public:
|
||||
// Constructor / Destructor
|
||||
OptionsDialog(QWidget *parent = nullptr);
|
||||
@@ -102,10 +107,10 @@ private slots:
|
||||
void on_randomButton_clicked();
|
||||
void on_addScanFolderButton_clicked();
|
||||
void on_removeScanFolderButton_clicked();
|
||||
void on_btnWebUiCrt_clicked();
|
||||
void on_btnWebUiKey_clicked();
|
||||
void on_registerDNSBtn_clicked();
|
||||
void setLocale(const QString &localeStr);
|
||||
void webUIHttpsCertChanged(const QString &path, ShowError showError);
|
||||
void webUIHttpsKeyChanged(const QString &path, ShowError showError);
|
||||
|
||||
private:
|
||||
// Methods
|
||||
@@ -164,17 +169,14 @@ private:
|
||||
int getMaxActiveDownloads() const;
|
||||
int getMaxActiveUploads() const;
|
||||
int getMaxActiveTorrents() const;
|
||||
// WebUI
|
||||
bool isWebUiEnabled() const;
|
||||
QString webUiUsername() const;
|
||||
QString webUiPassword() const;
|
||||
// WebUI SSL Cert / key
|
||||
bool setSslKey(const QByteArray &key);
|
||||
bool setSslCertificate(const QByteArray &cert);
|
||||
bool schedTimesOk();
|
||||
bool webUIAuthenticationOk();
|
||||
bool isAlternativeWebUIPathValid();
|
||||
|
||||
QByteArray m_sslCert, m_sslKey;
|
||||
bool schedTimesOk();
|
||||
|
||||
Ui::OptionsDialog *m_ui;
|
||||
QAbstractButton *m_applyButton;
|
||||
|
||||
@@ -2966,80 +2966,26 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblSslCertStatus"/>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblWebUiKey">
|
||||
<property name="text">
|
||||
<string>Key:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="lblWebUiCrt">
|
||||
<property name="text">
|
||||
<string>Certificate:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnWebUiCrt">
|
||||
<property name="text">
|
||||
<string>Import SSL Certificate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_12">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>138</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblSslCertStatus"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblSslKeyStatus"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="lblWebUiKey">
|
||||
<property name="text">
|
||||
<string>Key:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnWebUiKey">
|
||||
<property name="text">
|
||||
<string>Import SSL Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_13">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QLabel" name="lblWebUIInfo">
|
||||
<property name="text">
|
||||
@@ -3050,6 +2996,12 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="FileSystemPathLineEdit" name="textWebUIHttpsCert" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="FileSystemPathLineEdit" name="textWebUIHttpsKey" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -3464,8 +3416,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
||||
<tabstop>comboRatioLimitAct</tabstop>
|
||||
<tabstop>checkWebUIUPnP</tabstop>
|
||||
<tabstop>checkWebUiHttps</tabstop>
|
||||
<tabstop>btnWebUiCrt</tabstop>
|
||||
<tabstop>btnWebUiKey</tabstop>
|
||||
<tabstop>checkBypassLocalAuth</tabstop>
|
||||
<tabstop>checkBypassAuthSubnetWhitelist</tabstop>
|
||||
<tabstop>IPSubnetWhitelistButton</tabstop>
|
||||
|
||||
Reference in New Issue
Block a user