mirror of https://github.com/qt/qthttpserver.git
Add support for IP address whitelist and blacklist in QHttpServer
- Added methods for setting a whitelist and a blacklist in the QHttpServerConfiguration class. - Implemented IP address filtering in QHttpServer based on the configuration. Task-number: QTBUG-75087 Change-Id: I2b630bb15e34aa2f0633f64331bcc558bd5a1809 Reviewed-by: Øystein Heskestad <oystein.heskestad@qt.io> Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
eb6e4d157a
commit
2d9e986800
|
@ -15,6 +15,8 @@ class QHttpServerConfigurationPrivate : public QSharedData
|
|||
{
|
||||
public:
|
||||
quint32 rateLimit = 0;
|
||||
QList<QPair<QHostAddress, int>> whitelist;
|
||||
QList<QPair<QHostAddress, int>> blacklist;
|
||||
};
|
||||
|
||||
QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QHttpServerConfigurationPrivate)
|
||||
|
@ -85,6 +87,64 @@ quint32 QHttpServerConfiguration::rateLimitPerSecond() const
|
|||
return d->rateLimit;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets \a subnetList as the whitelist of allowed subnets.
|
||||
|
||||
When the list is not empty, only IP addresses in this list
|
||||
will be allowed by QHttpServer. The whitelist takes priority
|
||||
over the blacklist.
|
||||
|
||||
Each subnet is represented as a pair consisting of:
|
||||
\list
|
||||
\li A base IP address of type QHostAddress.
|
||||
\li A CIDR prefix length of type int, which defines the subnet mask.
|
||||
\endlist
|
||||
|
||||
To allow only a specific IP address, use a prefix length of 32 for IPv4
|
||||
(e.g., "192.168.1.100/32") or 128 for IPv6 (e.g., "2001:db8::1/128").
|
||||
|
||||
\sa whitelist(), setBlacklist(), QHostAddress::parseSubnet()
|
||||
*/
|
||||
void QHttpServerConfiguration::setWhitelist(const QList<std::pair<QHostAddress, int>> &subnetList)
|
||||
{
|
||||
d.detach();
|
||||
d->whitelist = subnetList;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the whitelist of subnets allowed by QHttpServer.
|
||||
|
||||
\sa setWhitelist()
|
||||
*/
|
||||
QList<std::pair<QHostAddress, int>> QHttpServerConfiguration::whitelist() const
|
||||
{
|
||||
return d->whitelist;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets \a subnetList as the blacklist of subnets.
|
||||
|
||||
IP addresses in this list will be denied access by QHttpServer.
|
||||
The blacklist is active only when the whitelist is empty.
|
||||
|
||||
\sa blacklist(), setWhitelist(), QHostAddress::parseSubnet()
|
||||
*/
|
||||
void QHttpServerConfiguration::setBlacklist(const QList<std::pair<QHostAddress, int>> &subnetList)
|
||||
{
|
||||
d.detach();
|
||||
d->blacklist = subnetList;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the blacklist of subnets that are denied access by QHttpServer.
|
||||
|
||||
\sa setBlacklist()
|
||||
*/
|
||||
QList<std::pair<QHostAddress, int>> QHttpServerConfiguration::blacklist() const
|
||||
{
|
||||
return d->blacklist;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn void QHttpServerConfiguration::swap(QHttpServerConfiguration &other)
|
||||
\memberswap{configuration}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <QtHttpServer/qthttpserverglobal.h>
|
||||
|
||||
#include <QtCore/qshareddata.h>
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtNetwork/qhostaddress.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
@ -29,6 +31,12 @@ public:
|
|||
Q_HTTPSERVER_EXPORT void setRateLimitPerSecond(quint32 maxRequests);
|
||||
Q_HTTPSERVER_EXPORT quint32 rateLimitPerSecond() const;
|
||||
|
||||
Q_HTTPSERVER_EXPORT void setWhitelist(const QList<std::pair<QHostAddress, int>> &subnetList);
|
||||
Q_HTTPSERVER_EXPORT QList<std::pair<QHostAddress, int>> whitelist() const;
|
||||
|
||||
Q_HTTPSERVER_EXPORT void setBlacklist(const QList<std::pair<QHostAddress, int>> &subnetList);
|
||||
Q_HTTPSERVER_EXPORT QList<std::pair<QHostAddress, int>> blacklist() const;
|
||||
|
||||
private:
|
||||
QExplicitlySharedDataPointer<QHttpServerConfigurationPrivate> d;
|
||||
|
||||
|
|
|
@ -367,6 +367,10 @@ void QHttpServerHttp1ProtocolHandler::handleReadyRead()
|
|||
|
||||
QHostAddress peerAddress = tcpSocket ? tcpSocket->peerAddress()
|
||||
: QHostAddress::LocalHost;
|
||||
if (!m_filter->isRequestAllowed(peerAddress)) {
|
||||
responder.sendResponse(
|
||||
QHttpServerResponse(QHttpServerResponder::StatusCode::Forbidden));
|
||||
}
|
||||
if (!m_filter->isRequestWithinRate(peerAddress)) {
|
||||
responder.sendResponse(
|
||||
QHttpServerResponse(QHttpServerResponder::StatusCode::TooManyRequests));
|
||||
|
|
|
@ -272,6 +272,10 @@ void QHttpServerHttp2ProtocolHandler::onStreamHalfClosed(quint32 streamId)
|
|||
QHttpServerResponder responder(this);
|
||||
responder.d_ptr->m_streamId = streamId;
|
||||
|
||||
if (!m_filter->isRequestAllowed(m_tcpSocket->peerAddress())) {
|
||||
responder.sendResponse(
|
||||
QHttpServerResponse(QHttpServerResponder::StatusCode::Forbidden));
|
||||
}
|
||||
if (!m_filter->isRequestWithinRate(m_tcpSocket->peerAddress())) {
|
||||
responder.sendResponse(
|
||||
QHttpServerResponse(QHttpServerResponder::StatusCode::TooManyRequests));
|
||||
|
|
|
@ -27,6 +27,24 @@ void QHttpServerRequestFilter::setConfiguration(const QHttpServerConfiguration &
|
|||
m_config = config;
|
||||
}
|
||||
|
||||
bool QHttpServerRequestFilter::isRequestAllowed(QHostAddress peerAddress)
|
||||
{
|
||||
if (auto whitelist = m_config.whitelist(); !whitelist.empty()) {
|
||||
for (auto &whitelistedSubnet : whitelist) {
|
||||
if (peerAddress.isInSubnet(whitelistedSubnet))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &blacklistedSubnet : m_config.blacklist()) {
|
||||
if (peerAddress.isInSubnet(blacklistedSubnet))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QHttpServerRequestFilter::isRequestWithinRate(QHostAddress peerAddress)
|
||||
{
|
||||
return isRequestWithinRate(peerAddress, QDateTime::currentMSecsSinceEpoch());
|
||||
|
|
|
@ -33,6 +33,8 @@ public:
|
|||
|
||||
void setConfiguration(const QHttpServerConfiguration &config);
|
||||
|
||||
bool isRequestAllowed(QHostAddress peerAddress);
|
||||
|
||||
bool isRequestWithinRate(QHostAddress peerAddress);
|
||||
bool isRequestWithinRate(QHostAddress peerAddress, const qint64 currTimeMSec);
|
||||
|
||||
|
|
|
@ -13,10 +13,11 @@ class tst_QHttpServerRequestFilter : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testRateLimit();
|
||||
void testIsRequestWithinRate();
|
||||
void testIsRequestAllowed();
|
||||
};
|
||||
|
||||
void tst_QHttpServerRequestFilter::testRateLimit()
|
||||
void tst_QHttpServerRequestFilter::testIsRequestWithinRate()
|
||||
{
|
||||
using namespace QHttpServerRequestFilterPrivate;
|
||||
|
||||
|
@ -86,6 +87,60 @@ void tst_QHttpServerRequestFilter::testRateLimit()
|
|||
QCOMPARE(filter4.isRequestWithinRate(QHostAddress("168.0.0.1"), currTimeMSec), true);
|
||||
}
|
||||
|
||||
void tst_QHttpServerRequestFilter::testIsRequestAllowed()
|
||||
{
|
||||
using namespace QHttpServerRequestFilterPrivate;
|
||||
|
||||
QHttpServerRequestFilter filter;
|
||||
QHttpServerConfiguration config;
|
||||
|
||||
// no blacklist or whitelist set
|
||||
filter.setConfiguration(config);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("127.0.0.1")), true);
|
||||
|
||||
// whitelist only
|
||||
QList<std::pair<QHostAddress, int>> whiteList = {
|
||||
{ QHostAddress("192.168.1.100"), 32 },
|
||||
{ QHostAddress("10.0.0.0"), 8 }
|
||||
};
|
||||
config.setWhitelist(whiteList);
|
||||
filter.setConfiguration(config);
|
||||
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("192.168.1.100")), true);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("192.168.1.101")), false);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("10.0.0.50")), true);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("11.0.0.1")), false);
|
||||
|
||||
// blacklist only
|
||||
QList<std::pair<QHostAddress, int>> blackList = {
|
||||
{ QHostAddress("192.168.1.200"), 32 },
|
||||
{ QHostAddress("172.16.0.0"), 12 }
|
||||
};
|
||||
config.setBlacklist(blackList);
|
||||
config.setWhitelist({});
|
||||
filter.setConfiguration(config);
|
||||
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("192.168.1.200")), false);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("192.168.1.201")), true);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("172.16.5.10")), false);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("172.32.0.1")), true);
|
||||
|
||||
// both whitelist and blacklist (whitelist should take priority)
|
||||
whiteList = {
|
||||
{ QHostAddress("10.0.0.1"), 32 }
|
||||
};
|
||||
blackList = {
|
||||
{ QHostAddress("10.0.0.0"), 8 }
|
||||
};
|
||||
config.setWhitelist(whiteList);
|
||||
config.setBlacklist(blackList);
|
||||
filter.setConfiguration(config);
|
||||
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("10.0.0.1")), true);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("10.0.0.2")), false);
|
||||
QCOMPARE(filter.isRequestAllowed(QHostAddress("192.168.0.1")), false);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
QTEST_MAIN(tst_QHttpServerRequestFilter)
|
||||
|
|
Loading…
Reference in New Issue