From 6cf909e34f1ff2ac9a79922feb0ad87ef2bff9db Mon Sep 17 00:00:00 2001 From: Kevin Yue Date: Sat, 11 Jun 2022 21:13:03 +0800 Subject: [PATCH] fix: refine the authentication workflow --- GPClient/gatewayauthenticator.cpp | 6 +-- GPClient/gatewayauthenticator.h | 2 +- GPClient/gpclient.cpp | 11 ++--- GPClient/portalauthenticator.cpp | 31 ++++++++------ GPClient/portalauthenticator.h | 7 +++- GPClient/samlloginwindow.cpp | 67 ++++++++++++++++++------------- GPClient/samlloginwindow.h | 7 +++- 7 files changed, 78 insertions(+), 53 deletions(-) diff --git a/GPClient/gatewayauthenticator.cpp b/GPClient/gatewayauthenticator.cpp index 69fa8f4..42dbffb 100644 --- a/GPClient/gatewayauthenticator.cpp +++ b/GPClient/gatewayauthenticator.cpp @@ -166,8 +166,8 @@ void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QSt this->onSAMLLoginSuccess(samlResult); loginWindow->deleteLater(); }); - connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &error) { - this->onSAMLLoginFail(error); + connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString &error) { + this->onSAMLLoginFail(code, error); loginWindow->deleteLater(); }); connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { @@ -194,7 +194,7 @@ void GatewayAuthenticator::onSAMLLoginSuccess(const QMap &saml login(loginParams); } -void GatewayAuthenticator::onSAMLLoginFail(const QString msg) +void GatewayAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) { emit fail(msg); } diff --git a/GPClient/gatewayauthenticator.h b/GPClient/gatewayauthenticator.h index df92a31..9023eca 100644 --- a/GPClient/gatewayauthenticator.h +++ b/GPClient/gatewayauthenticator.h @@ -28,7 +28,7 @@ private slots: void onLoginWindowRejected(); void onLoginWindowFinished(); void onSAMLLoginSuccess(const QMap &samlResult); - void onSAMLLoginFail(const QString msg); + void onSAMLLoginFail(const QString &code, const QString &msg); private: QString gateway; diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp index aab4ed7..124d2fa 100644 --- a/GPClient/gpclient.cpp +++ b/GPClient/gpclient.cpp @@ -221,7 +221,7 @@ void GPClient::onGatewayChanged(QAction *action) return; } - const GPGateway g = allGateways().at(index); + const auto g = allGateways().at(index); // If the selected gateway is the same as the current gateway if (g.name() == currentGateway().name()) { @@ -243,8 +243,8 @@ void GPClient::doConnect() { LOGI << "Start connecting..."; - const QString btnText = ui->connectButton->text(); - const QString portal = this->portal(); + const auto btnText = ui->connectButton->text(); + const auto portal = this->portal(); // Display the main window if portal is empty if (portal.isEmpty()) { @@ -277,7 +277,7 @@ void GPClient::doConnect() // Login to the portal interface to get the portal config and preferred gateway void GPClient::portalLogin() { - PortalAuthenticator *portalAuth = new PortalAuthenticator(portal(), settings::get("clientos", "Linux").toString()); + auto *portalAuth = new PortalAuthenticator(portal(), settings::get("clientos", "Linux").toString()); connect(portalAuth, &PortalAuthenticator::success, [this, portalAuth](const PortalConfigResponse response, const QString region) { this->onPortalSuccess(response, region); @@ -324,7 +324,7 @@ void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const QS void GPClient::onPortalPreloginFail(const QString msg) { - LOGI << "Portal prelogin failed: " << msg; + LOGI << "Portal prelogin failed, treat the portal address as a gateway." << msg; tryGatewayLogin(); } @@ -404,6 +404,7 @@ void GPClient::onGatewayFail(const QString &msg) // If the quick connect on gateway failed, perform the portal login if (isQuickConnect && !msg.isEmpty()) { isQuickConnect = false; + LOGI << "Quick connection failed, trying to portal login..."; portalLogin(); return; } diff --git a/GPClient/portalauthenticator.cpp b/GPClient/portalauthenticator.cpp index f8efad1..a1435ef 100644 --- a/GPClient/portalauthenticator.cpp +++ b/GPClient/portalauthenticator.cpp @@ -30,7 +30,9 @@ PortalAuthenticator::~PortalAuthenticator() void PortalAuthenticator::authenticate() { - LOGI << "Preform portal prelogin at " << preloginUrl; + attempts++; + + LOGI << QString("(%1/%2) attempts").arg(attempts).arg(MAX_ATTEMPTS) << ", preform portal prelogin at " << preloginUrl; QNetworkReply *reply = createRequest(preloginUrl); connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished); @@ -38,7 +40,7 @@ void PortalAuthenticator::authenticate() void PortalAuthenticator::onPreloginFinished() { - QNetworkReply *reply = qobject_cast(sender()); + auto *reply = qobject_cast(sender()); if (reply->error()) { LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString()); @@ -123,15 +125,15 @@ void PortalAuthenticator::samlAuth() SAMLLoginWindow *loginWindow = new SAMLLoginWindow; connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap samlResult) { - onSAMLLoginSuccess(samlResult); + this->onSAMLLoginSuccess(samlResult); loginWindow->deleteLater(); }); - connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString msg) { - onSAMLLoginFail(msg); + connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString msg) { + this->onSAMLLoginFail(code, msg); loginWindow->deleteLater(); }); connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { - onLoginWindowRejected(); + this->onLoginWindowRejected(); loginWindow->deleteLater(); }); @@ -141,17 +143,22 @@ void PortalAuthenticator::samlAuth() void PortalAuthenticator::onSAMLLoginSuccess(const QMap samlResult) { if (samlResult.contains("preloginCookie")) { - LOGI << "SAML login succeeded, got the prelogin-cookie " << samlResult.value("preloginCookie"); + LOGI << "SAML login succeeded, got the prelogin-cookie"; } else { - LOGI << "SAML login succeeded, got the portal-userauthcookie " << samlResult.value("userAuthCookie"); + LOGI << "SAML login succeeded, got the portal-userauthcookie"; } fetchConfig(samlResult.value("username"), "", samlResult.value("preloginCookie"), samlResult.value("userAuthCookie")); } -void PortalAuthenticator::onSAMLLoginFail(const QString msg) +void PortalAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) { - emitFail(msg); + if (code == "ERR002" && attempts < MAX_ATTEMPTS) { + LOGI << "Failed to authenticate, trying to re-authenticate..."; + authenticate(); + } else { + emitFail(msg); + } } void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie, QString userAuthCookie) @@ -167,9 +174,9 @@ void PortalAuthenticator::fetchConfig(QString username, QString password, QStrin this->username = username; this->password = password; - LOGI << "Fetching the portal config from " << configUrl << " for user: " << username; + LOGI << "Fetching the portal config from " << configUrl; - QNetworkReply *reply = createRequest(configUrl, loginParams.toUtf8()); + auto *reply = createRequest(configUrl, loginParams.toUtf8()); connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); } diff --git a/GPClient/portalauthenticator.h b/GPClient/portalauthenticator.h index 4a39fc3..7c4b823 100644 --- a/GPClient/portalauthenticator.h +++ b/GPClient/portalauthenticator.h @@ -30,10 +30,12 @@ private slots: void onLoginWindowRejected(); void onLoginWindowFinished(); void onSAMLLoginSuccess(const QMap samlResult); - void onSAMLLoginFail(const QString msg); + void onSAMLLoginFail(const QString &code, const QString &msg); void onFetchConfigFinished(); private: + static const auto MAX_ATTEMPTS{ 5 }; + QString portal; QString clientos; QString preloginUrl; @@ -41,9 +43,10 @@ private: QString username; QString password; + int attempts{ 0 }; PreloginResponse preloginResponse; - bool isAutoLogin { false }; + bool isAutoLogin{ false }; NormalLoginWindow *normalLoginWindow{ nullptr }; diff --git a/GPClient/samlloginwindow.cpp b/GPClient/samlloginwindow.cpp index 404256e..41bbd6b 100644 --- a/GPClient/samlloginwindow.cpp +++ b/GPClient/samlloginwindow.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "samlloginwindow.h" @@ -16,12 +17,20 @@ SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) QVBoxLayout *verticalLayout = new QVBoxLayout(this); webView->setUrl(QUrl("about:blank")); webView->setAttribute(Qt::WA_DeleteOnClose); - // webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); verticalLayout->addWidget(webView); webView->initialize(); connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished); + + // Show the login window automatically when exceeds the MAX_WAIT_TIME + QTimer::singleShot(MAX_WAIT_TIME, this, [this]() { + if (failed) { + return; + } + LOGI << "MAX_WAIT_TIME exceeded, display the login window."; + this->show(); + }); } SAMLLoginWindow::~SAMLLoginWindow() @@ -37,50 +46,53 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event) void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl) { + webView->page()->profile()->cookieStore()->deleteSessionCookies(); + if (samlMethod == "POST") { webView->setHtml(samlRequest, preloingUrl); } else if (samlMethod == "REDIRECT") { + LOGI << "Redirect to " << samlRequest; webView->load(samlRequest); } else { LOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; - emit fail("Unknown saml-auth-method, got " + samlMethod); + failed = true; + emit fail("ERR001", "Unknown saml-auth-method, got " + samlMethod); } } void SAMLLoginWindow::onResponseReceived(QJsonObject params) { - QString type = params.value("type").toString(); + const auto type = params.value("type").toString(); // Skip non-document response if (type != "Document") { return; } - QJsonObject response = params.value("response").toObject(); - QJsonObject headers = response.value("headers").toObject(); + auto response = params.value("response").toObject(); + auto headers = response.value("headers").toObject(); - LOGI << "Trying to receive from " << response.value("url").toString(); + LOGI << "Trying to receive authentication cookie from " << response.value("url").toString(); - const QString username = headers.value("saml-username").toString(); - const QString preloginCookie = headers.value("prelogin-cookie").toString(); - const QString userAuthCookie = headers.value("portal-userauthcookie").toString(); + const auto username = headers.value("saml-username").toString(); + const auto preloginCookie = headers.value("prelogin-cookie").toString(); + const auto userAuthCookie = headers.value("portal-userauthcookie").toString(); this->checkSamlResult(username, preloginCookie, userAuthCookie); } void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie) { + LOGI << "Checking the authentication result..."; + if (!username.isEmpty()) { - LOGI << "Got username from SAML response headers " << username; samlResult.insert("username", username); } if (!preloginCookie.isEmpty()) { - LOGI << "Got prelogin-cookie from SAML response headers " << preloginCookie; samlResult.insert("preloginCookie", preloginCookie); } if (!userAuthCookie.isEmpty()) { - LOGI << "Got portal-userauthcookie from SAML response headers " << userAuthCookie; samlResult.insert("userAuthCookie", userAuthCookie); } @@ -94,8 +106,6 @@ void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, emit success(samlResult); accept(); - } else { - show(); } } @@ -108,25 +118,24 @@ void SAMLLoginWindow::onLoadFinished() void SAMLLoginWindow::handleHtml(const QString &html) { // try to check the html body and extract from there - const QRegularExpression regex("(.*)"); - const QRegularExpressionMatch match = regex.match(html); - const QString samlAuthStatusOnBody = match.captured(1); + const auto samlAuthStatus = parseTag("saml-auth-status", html); - if (samlAuthStatusOnBody == "1") { - const QRegularExpression preloginCookieRegex("(.*)"); - const QRegularExpressionMatch preloginCookieMatch = preloginCookieRegex.match(html); - const QString preloginCookie = preloginCookieMatch.captured(1); - - const QRegularExpression usernameRegex("(.*)"); - const QRegularExpressionMatch usernameMatch = usernameRegex.match(html); - const QString username = usernameMatch.captured(1); - - const QRegularExpression userAuthCookieRegex("(.*)"); - const QRegularExpressionMatch userAuthCookieMatch = userAuthCookieRegex.match(html); - const QString userAuthCookie = userAuthCookieMatch.captured(1); + if (samlAuthStatus == "1") { + const auto preloginCookie = parseTag("prelogin-cookie", html); + const auto username = parseTag("saml-username", html); + const auto userAuthCookie = parseTag("portal-userauthcookie", html); checkSamlResult(username, preloginCookie, userAuthCookie); + } else if (samlAuthStatus == "-1") { + LOGI << "SAML authentication failed..."; + failed = true; + emit fail("ERR002", "Authentication failed, please try again."); } else { show(); } } + +QString SAMLLoginWindow::parseTag(const QString &tag, const QString &html) { + const QRegularExpression expression(QString("<%1>(.*)").arg(tag)); + return expression.match(html).captured(1); +} diff --git a/GPClient/samlloginwindow.h b/GPClient/samlloginwindow.h index 6feeba2..7bbf700 100644 --- a/GPClient/samlloginwindow.h +++ b/GPClient/samlloginwindow.h @@ -19,7 +19,7 @@ public: signals: void success(QMap samlResult); - void fail(const QString msg); + void fail(const QString code, const QString msg); private slots: void onResponseReceived(QJsonObject params); @@ -27,11 +27,16 @@ private slots: void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie); private: + static const auto MAX_WAIT_TIME { 10 * 1000 }; + + bool failed { false }; EnhancedWebView *webView; QMap samlResult; void closeEvent(QCloseEvent *event); void handleHtml(const QString &html); + + static QString parseTag(const QString &tag, const QString &html); }; #endif // SAMLLOGINWINDOW_H