diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 7742ac281..b76198e93 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -548,10 +548,9 @@ QVector Preferences::getWebUiAuthSubnetWhitelist() const for (const QString &rawSubnet : subnets) { - bool ok = false; - const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok); - if (ok) - ret.append(subnet); + const std::optional subnet = Utils::Net::parseSubnet(rawSubnet.trimmed()); + if (subnet) + ret.append(subnet.value()); } return ret; @@ -561,9 +560,7 @@ void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets) { Algorithm::removeIf(subnets, [](const QString &subnet) { - bool ok = false; - Utils::Net::parseSubnet(subnet.trimmed(), &ok); - return !ok; + return !Utils::Net::parseSubnet(subnet.trimmed()).has_value(); }); setValue(u"Preferences/WebUI/AuthSubnetWhitelist"_qs, subnets); diff --git a/src/base/utils/net.cpp b/src/base/utils/net.cpp index aab83656c..4a8e5690f 100644 --- a/src/base/utils/net.cpp +++ b/src/base/utils/net.cpp @@ -47,22 +47,15 @@ namespace Utils return !QHostAddress(ip).isNull(); } - Subnet parseSubnet(const QString &subnetStr, bool *ok) + std::optional parseSubnet(const QString &subnetStr) { - const Subnet invalid = qMakePair(QHostAddress(), -1); const Subnet subnet = QHostAddress::parseSubnet(subnetStr); - if (ok) - *ok = (subnet != invalid); + const Subnet invalid = {QHostAddress(), -1}; + if (subnet == invalid) + return std::nullopt; return subnet; } - bool canParseSubnet(const QString &subnetStr) - { - bool ok = false; - parseSubnet(subnetStr, &ok); - return ok; - } - bool isLoopbackAddress(const QHostAddress &addr) { return (addr == QHostAddress::LocalHost) @@ -70,7 +63,7 @@ namespace Utils || (addr == QHostAddress(u"::ffff:127.0.0.1"_qs)); } - bool isIPInRange(const QHostAddress &addr, const QVector &subnets) + bool isIPInSubnets(const QHostAddress &addr, const QVector &subnets) { QHostAddress protocolEquivalentAddress; bool addrConversionOk = false; diff --git a/src/base/utils/net.h b/src/base/utils/net.h index f9712d59c..f3c5d8ddd 100644 --- a/src/base/utils/net.h +++ b/src/base/utils/net.h @@ -28,8 +28,10 @@ #pragma once -#include +#include + #include +#include class QSslCertificate; class QSslKey; @@ -37,13 +39,13 @@ class QString; namespace Utils::Net { + // alias for `QHostAddress::parseSubnet()` return type using Subnet = QPair; bool isValidIP(const QString &ip); - Subnet parseSubnet(const QString &subnetStr, bool *ok = nullptr); - bool canParseSubnet(const QString &subnetStr); + std::optional parseSubnet(const QString &subnetStr); bool isLoopbackAddress(const QHostAddress &addr); - bool isIPInRange(const QHostAddress &addr, const QVector &subnets); + bool isIPInSubnets(const QHostAddress &addr, const QVector &subnets); QString subnetToString(const Subnet &subnet); QHostAddress canonicalIPv6Addr(const QHostAddress &addr); diff --git a/src/gui/ipsubnetwhitelistoptionsdialog.cpp b/src/gui/ipsubnetwhitelistoptionsdialog.cpp index 96da8fcbc..3a0e92a2f 100644 --- a/src/gui/ipsubnetwhitelistoptionsdialog.cpp +++ b/src/gui/ipsubnetwhitelistoptionsdialog.cpp @@ -90,16 +90,15 @@ void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted() void IPSubnetWhitelistOptionsDialog::on_buttonWhitelistIPSubnet_clicked() { - bool ok = false; - const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text(), &ok); - if (!ok) + const std::optional subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text()); + if (!subnet) { QMessageBox::critical(this, tr("Error"), tr("The entered subnet is invalid.")); return; } m_model->insertRow(m_model->rowCount()); - m_model->setData(m_model->index(m_model->rowCount() - 1, 0), Utils::Net::subnetToString(subnet)); + m_model->setData(m_model->index(m_model->rowCount() - 1, 0), Utils::Net::subnetToString(subnet.value())); m_ui->txtIPSubnet->clear(); m_modified = true; } @@ -114,5 +113,5 @@ void IPSubnetWhitelistOptionsDialog::on_buttonDeleteIPSubnet_clicked() void IPSubnetWhitelistOptionsDialog::on_txtIPSubnet_textChanged(const QString &subnetStr) { - m_ui->buttonWhitelistIPSubnet->setEnabled(Utils::Net::canParseSubnet(subnetStr)); + m_ui->buttonWhitelistIPSubnet->setEnabled(Utils::Net::parseSubnet(subnetStr).has_value()); } diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index 35b7664a5..284107c85 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -3397,7 +3397,7 @@ Use ';' to split multiple entries. Can use wildcard '*'. - Specify reverse proxy IPs in order to use forwarded client address (X-Forwarded-For attribute), use ';' to split multiple entries. + Specify reverse proxy IPs (or subnets, e.g. 0.0.0.0/24) in order to use forwarded client address (X-Forwarded-For header). Use ';' to split multiple entries. diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 179db7108..d35a48c87 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -42,7 +42,6 @@ #include #include "base/algorithm.h" -#include "base/global.h" #include "base/http/httperror.h" #include "base/logger.h" #include "base/preferences.h" @@ -402,15 +401,29 @@ void WebApplication::configure() m_isReverseProxySupportEnabled = pref->isWebUIReverseProxySupportEnabled(); if (m_isReverseProxySupportEnabled) { - m_trustedReverseProxyList.clear(); - const QStringList proxyList = pref->getWebUITrustedReverseProxiesList().split(u';', Qt::SkipEmptyParts); - for (const QString &proxy : proxyList) + m_trustedReverseProxyList.clear(); + m_trustedReverseProxyList.reserve(proxyList.size()); + + for (QString proxy : proxyList) { - QHostAddress ip; - if (ip.setAddress(proxy)) - m_trustedReverseProxyList.push_back(ip); + if (!proxy.contains(u'/')) + { + const QAbstractSocket::NetworkLayerProtocol protocol = QHostAddress(proxy).protocol(); + if (protocol == QAbstractSocket::IPv4Protocol) + { + proxy.append(u"/32"); + } + else if (protocol == QAbstractSocket::IPv6Protocol) + { + proxy.append(u"/128"); + } + } + + const std::optional subnet = Utils::Net::parseSubnet(proxy); + if (subnet) + m_trustedReverseProxyList.push_back(subnet.value()); } if (m_trustedReverseProxyList.isEmpty()) @@ -579,7 +592,7 @@ bool WebApplication::isAuthNeeded() { if (!m_isLocalAuthEnabled && Utils::Net::isLoopbackAddress(m_clientAddress)) return false; - if (m_isAuthSubnetWhitelistEnabled && Utils::Net::isIPInRange(m_clientAddress, m_authSubnetWhitelist)) + if (m_isAuthSubnetWhitelistEnabled && Utils::Net::isIPInSubnets(m_clientAddress, m_authSubnetWhitelist)) return false; return true; } @@ -728,7 +741,7 @@ QHostAddress WebApplication::resolveClientAddress() const return m_env.clientAddress; // Only reverse proxy can overwrite client address - if (!m_trustedReverseProxyList.contains(m_env.clientAddress)) + if (!Utils::Net::isIPInSubnets(m_env.clientAddress, m_trustedReverseProxyList)) return m_env.clientAddress; const QString forwardedFor = m_request.headers.value(Http::HEADER_X_FORWARDED_FOR); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 02a8d576b..f5b0034fd 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -34,11 +34,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "base/applicationcomponent.h" #include "base/global.h" @@ -233,7 +235,7 @@ private: // Reverse proxy bool m_isReverseProxySupportEnabled; - QVector m_trustedReverseProxyList; + QVector m_trustedReverseProxyList; QHostAddress m_clientAddress; QVector m_prebuiltHeaders; diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index 4f3939849..7328dc972 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -847,7 +847,11 @@ - + @@ -869,7 +873,7 @@
- +