fix: refine the authentication workflow

This commit is contained in:
Kevin Yue 2022-06-11 21:13:03 +08:00
parent 343a6d03c1
commit 6cf909e34f
7 changed files with 78 additions and 53 deletions

View File

@ -166,8 +166,8 @@ void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QSt
this->onSAMLLoginSuccess(samlResult); this->onSAMLLoginSuccess(samlResult);
loginWindow->deleteLater(); loginWindow->deleteLater();
}); });
connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &error) { connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString &error) {
this->onSAMLLoginFail(error); this->onSAMLLoginFail(code, error);
loginWindow->deleteLater(); loginWindow->deleteLater();
}); });
connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() {
@ -194,7 +194,7 @@ void GatewayAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> &saml
login(loginParams); login(loginParams);
} }
void GatewayAuthenticator::onSAMLLoginFail(const QString msg) void GatewayAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg)
{ {
emit fail(msg); emit fail(msg);
} }

View File

@ -28,7 +28,7 @@ private slots:
void onLoginWindowRejected(); void onLoginWindowRejected();
void onLoginWindowFinished(); void onLoginWindowFinished();
void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult); void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult);
void onSAMLLoginFail(const QString msg); void onSAMLLoginFail(const QString &code, const QString &msg);
private: private:
QString gateway; QString gateway;

View File

@ -221,7 +221,7 @@ void GPClient::onGatewayChanged(QAction *action)
return; 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 the selected gateway is the same as the current gateway
if (g.name() == currentGateway().name()) { if (g.name() == currentGateway().name()) {
@ -243,8 +243,8 @@ void GPClient::doConnect()
{ {
LOGI << "Start connecting..."; LOGI << "Start connecting...";
const QString btnText = ui->connectButton->text(); const auto btnText = ui->connectButton->text();
const QString portal = this->portal(); const auto portal = this->portal();
// Display the main window if portal is empty // Display the main window if portal is empty
if (portal.isEmpty()) { if (portal.isEmpty()) {
@ -277,7 +277,7 @@ void GPClient::doConnect()
// Login to the portal interface to get the portal config and preferred gateway // Login to the portal interface to get the portal config and preferred gateway
void GPClient::portalLogin() 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) { connect(portalAuth, &PortalAuthenticator::success, [this, portalAuth](const PortalConfigResponse response, const QString region) {
this->onPortalSuccess(response, region); this->onPortalSuccess(response, region);
@ -324,7 +324,7 @@ void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const QS
void GPClient::onPortalPreloginFail(const QString msg) void GPClient::onPortalPreloginFail(const QString msg)
{ {
LOGI << "Portal prelogin failed: " << msg; LOGI << "Portal prelogin failed, treat the portal address as a gateway." << msg;
tryGatewayLogin(); tryGatewayLogin();
} }
@ -404,6 +404,7 @@ void GPClient::onGatewayFail(const QString &msg)
// If the quick connect on gateway failed, perform the portal login // If the quick connect on gateway failed, perform the portal login
if (isQuickConnect && !msg.isEmpty()) { if (isQuickConnect && !msg.isEmpty()) {
isQuickConnect = false; isQuickConnect = false;
LOGI << "Quick connection failed, trying to portal login...";
portalLogin(); portalLogin();
return; return;
} }

View File

@ -30,7 +30,9 @@ PortalAuthenticator::~PortalAuthenticator()
void PortalAuthenticator::authenticate() 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); QNetworkReply *reply = createRequest(preloginUrl);
connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished); connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished);
@ -38,7 +40,7 @@ void PortalAuthenticator::authenticate()
void PortalAuthenticator::onPreloginFinished() void PortalAuthenticator::onPreloginFinished()
{ {
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); auto *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) { if (reply->error()) {
LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString()); LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString());
@ -123,15 +125,15 @@ void PortalAuthenticator::samlAuth()
SAMLLoginWindow *loginWindow = new SAMLLoginWindow; SAMLLoginWindow *loginWindow = new SAMLLoginWindow;
connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> samlResult) { connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap<QString, QString> samlResult) {
onSAMLLoginSuccess(samlResult); this->onSAMLLoginSuccess(samlResult);
loginWindow->deleteLater(); loginWindow->deleteLater();
}); });
connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString msg) { connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString msg) {
onSAMLLoginFail(msg); this->onSAMLLoginFail(code, msg);
loginWindow->deleteLater(); loginWindow->deleteLater();
}); });
connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() {
onLoginWindowRejected(); this->onLoginWindowRejected();
loginWindow->deleteLater(); loginWindow->deleteLater();
}); });
@ -141,18 +143,23 @@ void PortalAuthenticator::samlAuth()
void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> samlResult) void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> samlResult)
{ {
if (samlResult.contains("preloginCookie")) { if (samlResult.contains("preloginCookie")) {
LOGI << "SAML login succeeded, got the prelogin-cookie " << samlResult.value("preloginCookie"); LOGI << "SAML login succeeded, got the prelogin-cookie";
} else { } 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")); 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)
{ {
if (code == "ERR002" && attempts < MAX_ATTEMPTS) {
LOGI << "Failed to authenticate, trying to re-authenticate...";
authenticate();
} else {
emitFail(msg); emitFail(msg);
} }
}
void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie, QString userAuthCookie) 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->username = username;
this->password = password; 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); connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished);
} }

View File

@ -30,10 +30,12 @@ private slots:
void onLoginWindowRejected(); void onLoginWindowRejected();
void onLoginWindowFinished(); void onLoginWindowFinished();
void onSAMLLoginSuccess(const QMap<QString, QString> samlResult); void onSAMLLoginSuccess(const QMap<QString, QString> samlResult);
void onSAMLLoginFail(const QString msg); void onSAMLLoginFail(const QString &code, const QString &msg);
void onFetchConfigFinished(); void onFetchConfigFinished();
private: private:
static const auto MAX_ATTEMPTS{ 5 };
QString portal; QString portal;
QString clientos; QString clientos;
QString preloginUrl; QString preloginUrl;
@ -41,6 +43,7 @@ private:
QString username; QString username;
QString password; QString password;
int attempts{ 0 };
PreloginResponse preloginResponse; PreloginResponse preloginResponse;
bool isAutoLogin{ false }; bool isAutoLogin{ false };

View File

@ -1,6 +1,7 @@
#include <QtWidgets/QVBoxLayout> #include <QtWidgets/QVBoxLayout>
#include <QtWebEngineWidgets/QWebEngineProfile> #include <QtWebEngineWidgets/QWebEngineProfile>
#include <QtWebEngineWidgets/QWebEngineView> #include <QtWebEngineWidgets/QWebEngineView>
#include <QWebEngineCookieStore>
#include <plog/Log.h> #include <plog/Log.h>
#include "samlloginwindow.h" #include "samlloginwindow.h"
@ -16,12 +17,20 @@ SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
QVBoxLayout *verticalLayout = new QVBoxLayout(this); QVBoxLayout *verticalLayout = new QVBoxLayout(this);
webView->setUrl(QUrl("about:blank")); webView->setUrl(QUrl("about:blank"));
webView->setAttribute(Qt::WA_DeleteOnClose); webView->setAttribute(Qt::WA_DeleteOnClose);
// webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
verticalLayout->addWidget(webView); verticalLayout->addWidget(webView);
webView->initialize(); webView->initialize();
connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived);
connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished); 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() SAMLLoginWindow::~SAMLLoginWindow()
@ -37,50 +46,53 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event)
void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl) void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl)
{ {
webView->page()->profile()->cookieStore()->deleteSessionCookies();
if (samlMethod == "POST") { if (samlMethod == "POST") {
webView->setHtml(samlRequest, preloingUrl); webView->setHtml(samlRequest, preloingUrl);
} else if (samlMethod == "REDIRECT") { } else if (samlMethod == "REDIRECT") {
LOGI << "Redirect to " << samlRequest;
webView->load(samlRequest); webView->load(samlRequest);
} else { } else {
LOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; 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) void SAMLLoginWindow::onResponseReceived(QJsonObject params)
{ {
QString type = params.value("type").toString(); const auto type = params.value("type").toString();
// Skip non-document response // Skip non-document response
if (type != "Document") { if (type != "Document") {
return; return;
} }
QJsonObject response = params.value("response").toObject(); auto response = params.value("response").toObject();
QJsonObject headers = response.value("headers").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 auto username = headers.value("saml-username").toString();
const QString preloginCookie = headers.value("prelogin-cookie").toString(); const auto preloginCookie = headers.value("prelogin-cookie").toString();
const QString userAuthCookie = headers.value("portal-userauthcookie").toString(); const auto userAuthCookie = headers.value("portal-userauthcookie").toString();
this->checkSamlResult(username, preloginCookie, userAuthCookie); this->checkSamlResult(username, preloginCookie, userAuthCookie);
} }
void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie) void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie)
{ {
LOGI << "Checking the authentication result...";
if (!username.isEmpty()) { if (!username.isEmpty()) {
LOGI << "Got username from SAML response headers " << username;
samlResult.insert("username", username); samlResult.insert("username", username);
} }
if (!preloginCookie.isEmpty()) { if (!preloginCookie.isEmpty()) {
LOGI << "Got prelogin-cookie from SAML response headers " << preloginCookie;
samlResult.insert("preloginCookie", preloginCookie); samlResult.insert("preloginCookie", preloginCookie);
} }
if (!userAuthCookie.isEmpty()) { if (!userAuthCookie.isEmpty()) {
LOGI << "Got portal-userauthcookie from SAML response headers " << userAuthCookie;
samlResult.insert("userAuthCookie", userAuthCookie); samlResult.insert("userAuthCookie", userAuthCookie);
} }
@ -94,8 +106,6 @@ void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie,
emit success(samlResult); emit success(samlResult);
accept(); accept();
} else {
show();
} }
} }
@ -108,25 +118,24 @@ void SAMLLoginWindow::onLoadFinished()
void SAMLLoginWindow::handleHtml(const QString &html) void SAMLLoginWindow::handleHtml(const QString &html)
{ {
// try to check the html body and extract from there // try to check the html body and extract from there
const QRegularExpression regex("<saml-auth-status>(.*)</saml-auth-status>"); const auto samlAuthStatus = parseTag("saml-auth-status", html);
const QRegularExpressionMatch match = regex.match(html);
const QString samlAuthStatusOnBody = match.captured(1);
if (samlAuthStatusOnBody == "1") { if (samlAuthStatus == "1") {
const QRegularExpression preloginCookieRegex("<prelogin-cookie>(.*)</prelogin-cookie>"); const auto preloginCookie = parseTag("prelogin-cookie", html);
const QRegularExpressionMatch preloginCookieMatch = preloginCookieRegex.match(html); const auto username = parseTag("saml-username", html);
const QString preloginCookie = preloginCookieMatch.captured(1); const auto userAuthCookie = parseTag("portal-userauthcookie", html);
const QRegularExpression usernameRegex("<saml-username>(.*)</saml-username>");
const QRegularExpressionMatch usernameMatch = usernameRegex.match(html);
const QString username = usernameMatch.captured(1);
const QRegularExpression userAuthCookieRegex("<portal-userauthcookie>(.*)</portal-userauthcookie>");
const QRegularExpressionMatch userAuthCookieMatch = userAuthCookieRegex.match(html);
const QString userAuthCookie = userAuthCookieMatch.captured(1);
checkSamlResult(username, preloginCookie, userAuthCookie); checkSamlResult(username, preloginCookie, userAuthCookie);
} else if (samlAuthStatus == "-1") {
LOGI << "SAML authentication failed...";
failed = true;
emit fail("ERR002", "Authentication failed, please try again.");
} else { } else {
show(); show();
} }
} }
QString SAMLLoginWindow::parseTag(const QString &tag, const QString &html) {
const QRegularExpression expression(QString("<%1>(.*)</%1>").arg(tag));
return expression.match(html).captured(1);
}

View File

@ -19,7 +19,7 @@ public:
signals: signals:
void success(QMap<QString, QString> samlResult); void success(QMap<QString, QString> samlResult);
void fail(const QString msg); void fail(const QString code, const QString msg);
private slots: private slots:
void onResponseReceived(QJsonObject params); void onResponseReceived(QJsonObject params);
@ -27,11 +27,16 @@ private slots:
void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie); void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie);
private: private:
static const auto MAX_WAIT_TIME { 10 * 1000 };
bool failed { false };
EnhancedWebView *webView; EnhancedWebView *webView;
QMap<QString, QString> samlResult; QMap<QString, QString> samlResult;
void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event);
void handleHtml(const QString &html); void handleHtml(const QString &html);
static QString parseTag(const QString &tag, const QString &html);
}; };
#endif // SAMLLOGINWINDOW_H #endif // SAMLLOGINWINDOW_H