mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
fix: refine the authentication workflow
This commit is contained in:
parent
343a6d03c1
commit
6cf909e34f
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 };
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user