diff options
author | Ivan Solovev <ivan.solovev@qt.io> | 2022-09-05 11:21:52 +0200 |
---|---|---|
committer | Ivan Solovev <ivan.solovev@qt.io> | 2022-09-14 21:55:03 +0200 |
commit | db2f209b2d299def846503e632dc9999e3007cba (patch) | |
tree | 6d6f4f5754ad6d2f70271fd0b237be6a5a2382e1 /tests | |
parent | 4c4af1619811e89bd436360e1c75ab0a235700ce (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.txt | 4 | ||||
-rw-r--r-- | tests/auto/oauth2/certs/selfsigned-client.crt | 18 | ||||
-rw-r--r-- | tests/auto/oauth2/certs/selfsigned-client.key | 27 | ||||
-rw-r--r-- | tests/auto/oauth2/certs/selfsigned-server.crt | 18 | ||||
-rw-r--r-- | tests/auto/oauth2/certs/selfsigned-server.key | 27 | ||||
-rw-r--r-- | tests/auto/oauth2/tst_oauth2.cpp | 136 | ||||
-rw-r--r-- | tests/auto/shared/tlswebserver.h | 121 | ||||
-rw-r--r-- | tests/auto/shared/webserver.h | 1 |
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 { |