summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2022-09-05 11:21:52 +0200
committerIvan Solovev <ivan.solovev@qt.io>2022-09-14 21:55:03 +0200
commitdb2f209b2d299def846503e632dc9999e3007cba (patch)
tree6d6f4f5754ad6d2f70271fd0b237be6a5a2382e1 /tests
parent4c4af1619811e89bd436360e1c75ab0a235700ce (diff)
OAuth2: allow to specify TLS configuration
Specifying a custom TLS configuration is necessary when it is required to establish a Mutual TLS connection between the client and the Authentication Server. [ChangeLog][QAbstractOAuth2] Introduce a new sslConfiguration parameter which allows to specify a TLS configuration used during the authentication process. This patch also applies the new parameter to QOAuth2AuthorizationCodeFlow class. Fixes: QTBUG-88325 Change-Id: I5daac3d97e4df1ecc35597e168a2d111881d704a Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/oauth2/CMakeLists.txt4
-rw-r--r--tests/auto/oauth2/certs/selfsigned-client.crt18
-rw-r--r--tests/auto/oauth2/certs/selfsigned-client.key27
-rw-r--r--tests/auto/oauth2/certs/selfsigned-server.crt18
-rw-r--r--tests/auto/oauth2/certs/selfsigned-server.key27
-rw-r--r--tests/auto/oauth2/tst_oauth2.cpp136
-rw-r--r--tests/auto/shared/tlswebserver.h121
-rw-r--r--tests/auto/shared/webserver.h1
8 files changed, 352 insertions, 0 deletions
diff --git a/tests/auto/oauth2/CMakeLists.txt b/tests/auto/oauth2/CMakeLists.txt
index 52ea366..b06a5ab 100644
--- a/tests/auto/oauth2/CMakeLists.txt
+++ b/tests/auto/oauth2/CMakeLists.txt
@@ -7,9 +7,12 @@
## tst_oauth2 Test:
#####################################################################
+list(APPEND test_data "certs")
+
qt_internal_add_test(tst_oauth2
SOURCES
../shared/webserver.h
+ ../shared/tlswebserver.h
tst_oauth2.cpp
INCLUDE_DIRECTORIES
../shared
@@ -18,6 +21,7 @@ qt_internal_add_test(tst_oauth2
Qt::Network
Qt::NetworkAuth
Qt::NetworkAuthPrivate
+ TESTDATA ${test_data}
)
#### Keys ignored in scope 1:.:.:oauth2.pro:<TRUE>:
diff --git a/tests/auto/oauth2/certs/selfsigned-client.crt b/tests/auto/oauth2/certs/selfsigned-client.crt
new file mode 100644
index 0000000..88da2db
--- /dev/null
+++ b/tests/auto/oauth2/certs/selfsigned-client.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6TCCAdECCC/r9KvmbWTKMA0GCSqGSIb3DQEBCwUAMDUxFDASBgNVBAMMC0F1
+c3dlaXNBcHAyMR0wGwYDVQQFExQxODIzNTE0MTY0NzI5NDg5NDM3MTAiGA8xOTcw
+MDEwMTAwMDAwMFoYDzk5OTkxMjMxMjM1OTU5WjA1MRQwEgYDVQQDDAtBdXN3ZWlz
+QXBwMjEdMBsGA1UEBRMUMTgyMzUxNDE2NDcyOTQ4OTQzNzEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCahBpcZyr+PJBCpolzQeFVvDKABwlpdRKGZ8qq
+jD4sq2L7VlBJslgJGv5vsB5oJbnX1FFEu4Uw2kYb/LhnFCEXEFtGKRpWOEZOOqWb
+4l4q2MCa82ZCoIDt8yoAt0sSShbtR6pjW+l0lwAOEpfGvMaMVo5JUyspRxhl1dSu
+sS2Wf65zliqF5VSM2r4xMfJ6LVytxDZsGfTe/HFT2OYYrF+UQZg0mNL39rYWOK4R
+xoOz8eLl3K5hKuHNfn5zPt5QtMhaIvebijBg23xJpl+BeoS37WzaK1f+NyWZKPFb
+rttvSnFxpkyRHqJJ5piNGH6pkQ1+zhd7uh7eOIwxktjYBOFzAgMBAAEwDQYJKoZI
+hvcNAQELBQADggEBADw3MYPft+X78OK/2HAltzsKjfxv/D5qVizm9hcyG1GYe5pS
+qgFn0trCyJopYdbRr+hP7CuHwMmv62CZiHSog3CBPoUh19JENUDGbHXxTEFleB0i
+Fd8I2+WvRjbQ+ehaeTJPx88v5kkJnB2tZUNZuhEws8emCwr1G0TQv1tRYCR1Lp9i
+8/I3FSFpL1zyk47WfM/THa279MPw9WtrFGA6oi36gH9mYxek7n/zQTVi54xDx9GT
+KigBYqavjFdNXryjLTCCtJpMTDePgP66NAUnxn0D/amI2vSbIN++PSTsBm+n4Ti5
+QW/ShFQDNb4bDiwjtTKCeKwvAp2/6GSHVkYy28M=
+-----END CERTIFICATE-----
diff --git a/tests/auto/oauth2/certs/selfsigned-client.key b/tests/auto/oauth2/certs/selfsigned-client.key
new file mode 100644
index 0000000..9e59342
--- /dev/null
+++ b/tests/auto/oauth2/certs/selfsigned-client.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAmoQaXGcq/jyQQqaJc0HhVbwygAcJaXUShmfKqow+LKti+1ZQ
+SbJYCRr+b7AeaCW519RRRLuFMNpGG/y4ZxQhFxBbRikaVjhGTjqlm+JeKtjAmvNm
+QqCA7fMqALdLEkoW7UeqY1vpdJcADhKXxrzGjFaOSVMrKUcYZdXUrrEtln+uc5Yq
+heVUjNq+MTHyei1crcQ2bBn03vxxU9jmGKxflEGYNJjS9/a2FjiuEcaDs/Hi5dyu
+YSrhzX5+cz7eULTIWiL3m4owYNt8SaZfgXqEt+1s2itX/jclmSjxW67bb0pxcaZM
+kR6iSeaYjRh+qZENfs4Xe7oe3jiMMZLY2AThcwIDAQABAoIBAFjgvc0C5t8AdTZx
+VsS+U2Aedang4lAPsE0xbIj3TFgjaTcLKfmKJUtvhIU39/WOJbz4+pofhvhXxVYZ
+4vQfxvzeQrIzuFt52S7sWxA0gFgC/57hfKO8cQzt/u4UgJEPnupze5XVa47NwJFX
+rof5U/erXgLdXQlMRMNm4QRvE7rp58E2MkSYNur0Xgy9L7cRcUQJ8iuMaxBpOzhS
+fbNFi5zT7RCGcQSIDcb1JFlgs5tMUs6jzLoDSVD2+vvsN4i4LAAPkJSGTGed5vY1
+xn4G8KPR4HHrnBYEb0SGu4ZTznOnQ+JSKhQrbnvEzXM4RTfjqn0YvF8x70+pWSMi
+Fb4mlBECgYEAzW82O79HAlMm8LD7J4byPfVc/1M5/JOnE9H+RR5Vt4jZQGyjCmJu
+cj4UeZyVim0xg30sSYrJ2Urd27CtHp+sMgHkvJt3/ZgcfMZJbMKNGq/OUtV8s/cA
+nkU++/LgeW8r7wpaDjT7bfnOdcf16mYoXrmk0rTJvRqGXCBvCxtt5bsCgYEAwIxu
+vZjPV4Vu/VX6sH2d31D9EFZuZKjGhqukFVtRqLbeosqT9mA+LhQ/wP5qoR2gLQbe
+EwxJLJwGFjUhyhbHNlo6oHv3fWkzmHIMPwDRRI3Ktwi/50SwNSnyERUQcLaiwqKx
+BqaxPYNnspUt0nKE0LFZsSlrfEyxajqAlUEgm6kCgYAV+uQumFScpxDvh8AXhpS8
+lFgS6XC22YVy1XEDLC+3p2i3P+hh4A45IvNF378QRIabrvTiGXtnSF9cdhbPw/3E
+i/dRRsEb3P6PSxfoDxjR1iWZL0Zcav0h8f6/LkleNMralJz2EC0moye36mEhZzTC
+jdJYyQccuI3PpZi7839aqQKBgGezOnEiO4kHdB88jyc+gCglliWWZx4PR9x/1H8s
+D26uDnneYJHwg4yNm0h1vTfInNujNzdLBp3f6edL9kbAvcmoDqsgGMqSPhd8VNwZ
+tJsXQnYRYElN1RjM3nIUxiXuNvpcZLsQS6S1gMPNVEBjLOS4n3WquRjYtTRhDZ9U
+1BsBAoGAUFrIatOLFhcgaqENHyUbMx5uSx0lIfF6Xd5KIAgi+btdmugHe+NK8Cd2
+Rc2bQLQ9K1SvKFX6nFuEsGxnXkKuyhL/j0Kgm8nZin4uAcrtFnNdFumvCL6YgYSc
+IvvM+uVfGEdbqm4pTuiLBfzOXIIy3kVlLGo402QG1pBzOtmsRMs=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/oauth2/certs/selfsigned-server.crt b/tests/auto/oauth2/certs/selfsigned-server.crt
new file mode 100644
index 0000000..c97d277
--- /dev/null
+++ b/tests/auto/oauth2/certs/selfsigned-server.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5TCCAc0CCAO22gNi0v20MA0GCSqGSIb3DQEBCwUAMDMxFDASBgNVBAMMC0F1
+c3dlaXNBcHAyMRswGQYDVQQFExIyNTIxMTE1NjY3NjM2MjExODgwIhgPMTk3MDAx
+MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowMzEUMBIGA1UEAwwLQXVzd2Vpc0Fw
+cDIxGzAZBgNVBAUTEjI1MjExMTU2Njc2MzYyMTE4ODCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAL+Fl6v5dcU7qk7vbINclWOhvCe/uklKnXV2QU382x7g
+qpbYxJiJvz24C6tgDMmE0pwEz6PiCbh1dkc8+9cdp37eBcFLCOXYQb27gqVVyVtu
+xO0LLVXPCv48bGSwljOz0FRC3FolzWxzrZogM/i2b/lmehHJ3D4ejmINmIgtFJ9P
+JNNCH4Oh5YEbaFFlNf2m7lCoSuQkOlLZcGeLoipK2XvhZJff6c1uxValh/Mx5dNB
+5Mgd5cOZSSEhwf7mcE8C3SHVfjeNfZGIqlkwdY8lvAOjirAtj6Yl88sJOUID/Q/N
+hU9D8IZy6+Bk2cJQwI/Gzr590VYvlSTI+6lXr//oBBECAwEAATANBgkqhkiG9w0B
+AQsFAAOCAQEArSMO88AYT+9tPCl5lXtSRa0OycqKNlW58GujxIDuR8WX1eFmGSHQ
+uijo5KPYUnqydZzAewGC8NvC9WcLwFltNZ9igXikUHiAHc1JLfW7+7SgKpwOUb02
+rJkUkpPA/SmwkLSKYiR1prt5wgSulU1HPBESep05DfR8MCU5+KHkLyXDqtrbudJ4
+lQd9dSKJFn+cSjUC5JNxCPHoIISe7hfGFMLkd0/tVfSIXLVOAZG4K6zExUdjyPi8
+qEuPq6QCRyIJbYQc5HfnARgwK6GXHqkyLWlqK946Yz8VOba7Nan5uQ6xCjUMHw8Z
+z/673o/3DCaQ9N6dWahNQ09a9ZH8U1X4iA==
+-----END CERTIFICATE-----
diff --git a/tests/auto/oauth2/certs/selfsigned-server.key b/tests/auto/oauth2/certs/selfsigned-server.key
new file mode 100644
index 0000000..b7be118
--- /dev/null
+++ b/tests/auto/oauth2/certs/selfsigned-server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAv4WXq/l1xTuqTu9sg1yVY6G8J7+6SUqddXZBTfzbHuCqltjE
+mIm/PbgLq2AMyYTSnATPo+IJuHV2Rzz71x2nft4FwUsI5dhBvbuCpVXJW27E7Qst
+Vc8K/jxsZLCWM7PQVELcWiXNbHOtmiAz+LZv+WZ6EcncPh6OYg2YiC0Un08k00If
+g6HlgRtoUWU1/abuUKhK5CQ6UtlwZ4uiKkrZe+Fkl9/pzW7FVqWH8zHl00HkyB3l
+w5lJISHB/uZwTwLdIdV+N419kYiqWTB1jyW8A6OKsC2PpiXzywk5QgP9D82FT0Pw
+hnLr4GTZwlDAj8bOvn3RVi+VJMj7qVev/+gEEQIDAQABAoIBADdoXsjSEtBMwqiz
+e6FFV7LLR7P4M9ygSY2B+MKnNH1qYe/iJn4626jvZfDeiNSEKKoaejffXRCQaveR
+HQrO+XYqpV+WZayZM+vAI7vRZb+d/DrX0PXSQEvtDy7SJ6Itk0fNUBKEfTmy/bZp
+Op/pp9tvWkFrNNyD2o1jgY1j/WNY8g605m0oURJ9WQsMUu/Kzu+NMoaKTIoQGb3d
+dP71F4KaTXHYxj3B0c+y0NedKbrvnBsP6XbEpgJBaXjtD9z+z/aMF6dmuvpkx7uY
+qzwPMRw05QPyJ9x+1V/v4TytY5f596NgW2niVj77BunkZasTYIEX7bjByrlTeLdx
+xvPRpAECgYEA5KkM/ORbhN1oaw9+tQxA48oG2DFqChBr+vc4NU4j5SNFn9ks5nHI
+xdJNZ9k+bjVUkBP4m88Wd07SW9zXCL8Q5lczb+p5SWl/Pp7ltqaxpH17uzamsaIv
+KIBkeJTOU5TuWdXiV5FY+ofK9ojyEaqX1tmylWnoVe4bIMRWXE5bMSkCgYEA1mvJ
+snkNzPFG0RK7ikjsNxrhzE07+7RSnoM9WeW8y2lvQ9MjdR6eOgqnnlcdk2A7OVbf
+culNgLc0qx/PxZ4BV+8yLLb1EBBGvuVG+x4a6H2mLHdFCJekByZHaQNs9ogVLvdv
+3z8D59KknBUjtj9dCw90Z41yMM4kpWMG9yfSEKkCgYEAvuCvytwF2d/JrrV8nD3i
+XUTkecymLEiRGysMbNMR+9F56XotlSEe7KQloa8kAnPaZ3uEaOxyYJ4X1D+B8fct
+cFsSwTYGkVXTtr6GG/cDC8EEbL+uX1J382Nae54croEAh1WYYGkg0eJRd4PSLxUt
+M1j/TuLd4/2j/7JmNR/j2CECgYBdB3MBHghgzKXe+/OmMbFazyz8SN4nfLsDzwkF
+QenBj0MY+DhADkK0B/9lcYKBeJT5cbmMz7AykkolnK22nbETh9ILGG4GxCkNlchQ
+F2WxTSKV1EF9Ut11xKPi6fuSksQuFmjRQTPelsOYfIt7/M3PiKsGapYKmsXHg8l3
+3i0D0QKBgQCi+HNOaYqduxwjrj8h4eUbiwjID8DCNJ+jXsuGVa6jcsfFpdpivx2c
+ytYSXuTXLRq0I3c1ChUOGQQeztJ5GtCPnXjLHHMf3f6yr7Pk56AUmUsaIlR1Q2Zo
+gqpFD8zYD5UFc2KM7Y38YTh4j82uDzDvHBBFpli7dEmSn2WpcmzFag==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/auto/oauth2/tst_oauth2.cpp b/tests/auto/oauth2/tst_oauth2.cpp
index 2fc22dc..d61a06d 100644
--- a/tests/auto/oauth2/tst_oauth2.cpp
+++ b/tests/auto/oauth2/tst_oauth2.cpp
@@ -3,20 +3,33 @@
#include <QtTest>
+#ifndef QT_NO_SSL
+#include <QSslKey>
+#endif
+
#include <QtNetworkAuth/qabstractoauthreplyhandler.h>
#include <QtNetworkAuth/qoauth2authorizationcodeflow.h>
#include "webserver.h"
+#include "tlswebserver.h"
class tst_OAuth2 : public QObject
{
Q_OBJECT
private Q_SLOTS:
+ void initTestCase();
void getToken();
void refreshToken();
void getAndRefreshToken();
void prepareRequest();
+#ifndef QT_NO_SSL
+ void setSslConfig();
+ void tlsAuthentication();
+#endif
+
+private:
+ QString testDataDir;
};
struct ReplyHandler : QAbstractOAuthReplyHandler
@@ -41,6 +54,15 @@ struct ReplyHandler : QAbstractOAuthReplyHandler
}
};
+void tst_OAuth2::initTestCase()
+{
+ testDataDir = QFileInfo(QFINDTESTDATA("certs")).absolutePath();
+ if (testDataDir.isEmpty())
+ testDataDir = QCoreApplication::applicationDirPath();
+ if (!testDataDir.endsWith(QLatin1String("/")))
+ testDataDir += QLatin1String("/");
+}
+
void tst_OAuth2::getToken()
{
WebServer webServer([](const WebServer::HttpRequest &request, QTcpSocket *socket) {
@@ -151,5 +173,119 @@ void tst_OAuth2::prepareRequest()
QCOMPARE(request.rawHeader("Authorization"), QByteArray("Bearer access_token"));
}
+#ifndef QT_NO_SSL
+static QSslConfiguration createSslConfiguration(QString keyFileName, QString certificateFileName)
+{
+ QSslConfiguration configuration(QSslConfiguration::defaultConfiguration());
+
+ QFile keyFile(keyFileName);
+ if (keyFile.open(QIODevice::ReadOnly)) {
+ QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
+ if (!key.isNull()) {
+ configuration.setPrivateKey(key);
+ } else {
+ qCritical() << "Could not parse key: " << keyFileName;
+ }
+ } else {
+ qCritical() << "Could not find key: " << keyFileName;
+ }
+
+ QList<QSslCertificate> localCert = QSslCertificate::fromPath(certificateFileName);
+ if (!localCert.isEmpty() && !localCert.first().isNull()) {
+ configuration.setLocalCertificate(localCert.first());
+ } else {
+ qCritical() << "Could not find certificate: " << certificateFileName;
+ }
+
+ configuration.setPeerVerifyMode(QSslSocket::VerifyPeer);
+
+ return configuration;
+}
+
+void tst_OAuth2::setSslConfig()
+{
+ QOAuth2AuthorizationCodeFlow oauth2;
+ QSignalSpy sslConfigSpy(&oauth2, &QAbstractOAuth2::sslConfigurationChanged);
+
+ QVERIFY(sslConfigSpy.isValid());
+ QCOMPARE(oauth2.sslConfiguration(), QSslConfiguration());
+ QCOMPARE(sslConfigSpy.size(), 0);
+
+ auto config = createSslConfiguration(testDataDir + "certs/selfsigned-server.key",
+ testDataDir + "certs/selfsigned-server.crt");
+ oauth2.setSslConfiguration(config);
+
+ QCOMPARE(oauth2.sslConfiguration(), config);
+ QCOMPARE(sslConfigSpy.size(), 1);
+
+ // set same config - nothing happens
+ oauth2.setSslConfiguration(config);
+ QCOMPARE(sslConfigSpy.size(), 1);
+
+ // change config
+ config.setPeerVerifyMode(QSslSocket::VerifyNone);
+ oauth2.setSslConfiguration(config);
+ QCOMPARE(oauth2.sslConfiguration(), config);
+ QCOMPARE(sslConfigSpy.size(), 2);
+}
+
+void tst_OAuth2::tlsAuthentication()
+{
+ if (!QSslSocket::supportsSsl())
+ QSKIP("This test will fail because the backend does not support TLS");
+
+ // erros may vary, depending on backend
+ const QSet<QSslError::SslError> expectedErrors{ QSslError::SelfSignedCertificate,
+ QSslError::CertificateUntrusted,
+ QSslError::HostNameMismatch };
+ auto serverConfig = createSslConfiguration(testDataDir + "certs/selfsigned-server.key",
+ testDataDir + "certs/selfsigned-server.crt");
+ TlsWebServer tlsServer([](const WebServer::HttpRequest &request, QTcpSocket *socket) {
+ if (request.url.path() == QLatin1String("/accessToken")) {
+ const QString text = "access_token=token&token_type=bearer";
+ const QByteArray replyMessage {
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/x-www-form-urlencoded; charset=\"utf-8\"\r\n"
+ "Content-Length: " + QByteArray::number(text.size()) + "\r\n\r\n"
+ + text.toUtf8()
+ };
+ socket->write(replyMessage);
+ }
+ }, serverConfig);
+ tlsServer.setExpectedSslErrors(expectedErrors);
+
+ auto clientConfig = createSslConfiguration(testDataDir + "certs/selfsigned-client.key",
+ testDataDir + "certs/selfsigned-client.crt");
+ QNetworkAccessManager nam;
+ QOAuth2AuthorizationCodeFlow oauth2;
+ oauth2.setNetworkAccessManager(&nam);
+ oauth2.setSslConfiguration(clientConfig);
+ oauth2.setAuthorizationUrl(tlsServer.url(QLatin1String("authorization")));
+ oauth2.setAccessTokenUrl(tlsServer.url(QLatin1String("accessToken")));
+ ReplyHandler replyHandler;
+ oauth2.setReplyHandler(&replyHandler);
+ connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, [&](const QUrl &url) {
+ const QUrlQuery query(url.query());
+ replyHandler.emitCallbackReceived(QVariantMap {
+ { QLatin1String("code"), QLatin1String("test") },
+ { QLatin1String("state"),
+ query.queryItemValue(QLatin1String("state")) }
+ });
+ });
+ connect(&nam, &QNetworkAccessManager::sslErrors,
+ [&expectedErrors](QNetworkReply *r, const QList<QSslError> &errors) {
+ QCOMPARE(errors.size(), 2);
+ for (const auto &err : errors)
+ QVERIFY(expectedErrors.contains(err.error()));
+ r->ignoreSslErrors();
+ });
+
+ QSignalSpy grantedSpy(&oauth2, &QOAuth2AuthorizationCodeFlow::granted);
+ oauth2.grant();
+ QTRY_COMPARE(grantedSpy.count(), 1);
+ QCOMPARE(oauth2.token(), QLatin1String("token"));
+}
+#endif // !QT_NO_SSL
+
QTEST_MAIN(tst_OAuth2)
#include "tst_oauth2.moc"
diff --git a/tests/auto/shared/tlswebserver.h b/tests/auto/shared/tlswebserver.h
new file mode 100644
index 0000000..efc0fbb
--- /dev/null
+++ b/tests/auto/shared/tlswebserver.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TLSWEBSERVER_H
+#define TLSWEBSERVER_H
+
+#include <QtNetworkAuth/qoauthglobal.h>
+
+#ifndef QT_NO_SSL
+
+#include "webserver.h"
+
+#include <QSslServer>
+#include <QSslSocket>
+#include <QSslConfiguration>
+
+class TlsWebServer : public QSslServer
+{
+public:
+ using HttpRequest = WebServer::HttpRequest;
+ using Handler = std::function<void(const HttpRequest &request, QTcpSocket *socket)>;
+
+ TlsWebServer(Handler handler, const QSslConfiguration &config, QObject *parent = nullptr);
+ QUrl url(const QString &path);
+ void setExpectedSslErrors(const QSet<QSslError::SslError> &errors);
+
+private:
+ Handler handler;
+ QMap<QTcpSocket *, HttpRequest> clients;
+ QSet<QSslError::SslError> expectedSslErrors;
+};
+
+TlsWebServer::TlsWebServer(Handler h, const QSslConfiguration &config, QObject *parent) :
+ QSslServer(parent),
+ handler(h)
+{
+ connect(this, &QSslServer::pendingConnectionAvailable, [this]() {
+ auto socket = nextPendingConnection();
+ Q_ASSERT(socket);
+ auto sslSocket = qobject_cast<QSslSocket *>(socket);
+ Q_ASSERT(sslSocket);
+ connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
+ connect(sslSocket, &QSslSocket::sslErrors, [sslSocket](const QList<QSslError> &errors) {
+ qDebug() << errors;
+ sslSocket->ignoreSslErrors();
+ });
+ connect(socket, &QTcpSocket::readyRead, [this, socket]() {
+ if (!clients.contains(socket))
+ clients[socket].port = serverPort();
+
+ auto *request = &clients[socket];
+ auto ok = true;
+
+ while (socket->bytesAvailable()) {
+ if (Q_LIKELY(request->state == HttpRequest::State::ReadingMethod))
+ if (Q_UNLIKELY(!(ok = request->readMethod(socket))))
+ qWarning("Invalid Method");
+
+ if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingUrl))
+ if (Q_UNLIKELY(!(ok = request->readUrl(socket))))
+ qWarning("Invalid URL");
+
+ if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingStatus))
+ if (Q_UNLIKELY(!(ok = request->readStatus(socket))))
+ qWarning("Invalid Status");
+
+ if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingHeader))
+ if (Q_UNLIKELY(!(ok = request->readHeaders(socket))))
+ qWarning("Invalid Header");
+
+ if (Q_LIKELY(ok && request->state == HttpRequest::State::ReadingBody))
+ if (Q_UNLIKELY(!(ok = request->readBody(socket))))
+ qWarning("Invalid Body");
+ }
+ if (Q_UNLIKELY(!ok)) {
+ socket->disconnectFromHost();
+ clients.remove(socket);
+ } else if (Q_LIKELY(request->state == HttpRequest::State::AllDone)) {
+ Q_ASSERT(handler);
+ if (request->headers.contains("Host")) {
+ const auto parts = request->headers["Host"].split(':');
+ request->url.setHost(parts.at(0));
+ if (parts.size() == 2)
+ request->url.setPort(parts.at(1).toUInt());
+ }
+ handler(*request, socket);
+ socket->disconnectFromHost();
+ clients.remove(socket);
+ }
+ });
+ });
+ connect(this, &QSslServer::sslErrors, [this](QSslSocket *s, const QList<QSslError> &errors) {
+ bool hasOnlyExpectedErrors = true;
+ for (const auto &err : errors)
+ hasOnlyExpectedErrors &= expectedSslErrors.contains(err.error());
+ if (hasOnlyExpectedErrors)
+ s->ignoreSslErrors();
+ else
+ qWarning() << "Got unexpected SSL errors" << errors;
+ });
+
+ setSslConfiguration(config);
+ const bool ok = listen(QHostAddress::LocalHost);
+ Q_ASSERT(ok);
+}
+
+QUrl TlsWebServer::url(const QString &path)
+{
+ using namespace Qt::StringLiterals;
+ return QUrl(u"https://127.0.0.1:%1%2"_s.arg(serverPort()).arg(path.startsWith('/')
+ ? path : "/" + path));
+}
+
+void TlsWebServer::setExpectedSslErrors(const QSet<QSslError::SslError> &errors)
+{
+ expectedSslErrors = errors;
+}
+
+#endif // !QT_NO_SSL
+
+#endif // TLSWEBSERVER_H
diff --git a/tests/auto/shared/webserver.h b/tests/auto/shared/webserver.h
index f553189..da76ea0 100644
--- a/tests/auto/shared/webserver.h
+++ b/tests/auto/shared/webserver.h
@@ -15,6 +15,7 @@ class WebServer : public QTcpServer
public:
class HttpRequest {
friend class WebServer;
+ friend class TlsWebServer;
quint16 port = 0;
enum class State {