diff --git a/GPClient/gatewayauthenticator.cpp b/GPClient/gatewayauthenticator.cpp index 16b51e8..4b4c993 100644 --- a/GPClient/gatewayauthenticator.cpp +++ b/GPClient/gatewayauthenticator.cpp @@ -140,18 +140,16 @@ void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QSt { PLOGI << "Trying to perform SAML login with saml-method " << samlMethod; - SAMLLoginWindow *loginWindow = samlLogin(samlMethod, samlRequest, preloginUrl); + SAMLLoginWindow *loginWindow = new SAMLLoginWindow; - if (!loginWindow) { - openMessageBox("SAML Login failed for gateway"); - return; - } - - connect(loginWindow, &SAMLLoginWindow::success, this, &GatewayAuthenticator::onSAMLLoginFinished); + connect(loginWindow, &SAMLLoginWindow::success, this, &GatewayAuthenticator::onSAMLLoginSuccess); + connect(loginWindow, &SAMLLoginWindow::fail, this, &GatewayAuthenticator::onSAMLLoginFail); connect(loginWindow, &SAMLLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected); + + loginWindow->login(samlMethod, samlRequest, preloginUrl); } -void GatewayAuthenticator::onSAMLLoginFinished(const QMap &samlResult) +void GatewayAuthenticator::onSAMLLoginSuccess(const QMap &samlResult) { PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie"); @@ -161,3 +159,8 @@ void GatewayAuthenticator::onSAMLLoginFinished(const QMap &sam login(params); } + +void GatewayAuthenticator::onSAMLLoginFail(const QString msg) +{ + emit fail(msg); +} diff --git a/GPClient/gatewayauthenticator.h b/GPClient/gatewayauthenticator.h index 5407803..4c676fb 100644 --- a/GPClient/gatewayauthenticator.h +++ b/GPClient/gatewayauthenticator.h @@ -25,7 +25,8 @@ private slots: void onPerformNormalLogin(const QString &username, const QString &password); void onLoginWindowRejected(); void onLoginWindowFinished(); - void onSAMLLoginFinished(const QMap &samlResult); + void onSAMLLoginSuccess(const QMap &samlResult); + void onSAMLLoginFail(const QString msg); private: QString gateway; @@ -34,7 +35,7 @@ private: const PortalConfigResponse& portalConfig; - NormalLoginWindow *normalLoginWindow{nullptr}; + NormalLoginWindow *normalLoginWindow{ nullptr }; void login(const LoginParams& params); void doAuth(); diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp index ff20468..f183bee 100644 --- a/GPClient/gpclient.cpp +++ b/GPClient/gpclient.cpp @@ -12,8 +12,6 @@ using namespace gpclient::helper; GPClient::GPClient(QWidget *parent) : QMainWindow(parent) , ui(new Ui::GPClient) - , systemTrayIcon(new QSystemTrayIcon(parent)) - , contextMenu(new QMenu("GlobalProtect", parent)) { ui->setupUi(this); setWindowTitle("GlobalProtect"); @@ -29,29 +27,15 @@ GPClient::GPClient(QWidget *parent) connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected); connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable); - connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated); - // Initiallize the context menu of system tray. - openAction = contextMenu->addAction(QIcon::fromTheme("system-run"), "Open", this, &GPClient::activiate); - connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect); - contextMenu->addSeparator(); - quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit); - systemTrayIcon->setContextMenu(contextMenu); - systemTrayIcon->setToolTip("GlobalProtect"); - + initSystemTrayIcon(); initVpnStatus(); - systemTrayIcon->show(); } GPClient::~GPClient() { delete ui; delete vpn; - delete systemTrayIcon; - delete openAction; - delete connectAction; - delete quitAction; - delete contextMenu; } void GPClient::on_connectButton_clicked() @@ -64,85 +48,35 @@ void GPClient::on_portalInput_returnPressed() doConnect(); } -void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) +void GPClient::on_portalInput_editingFinished() { - switch (status) { - case VpnStatus::disconnected: - ui->statusLabel->setText("Not Connected"); - ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;"); - ui->connectButton->setText("Connect"); - ui->connectButton->setDisabled(false); - ui->portalInput->setReadOnly(false); - - systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" }); - connectAction->setEnabled(true); - connectAction->setText("Connect"); - break; - case VpnStatus::pending: - ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;"); - ui->connectButton->setDisabled(true); - ui->portalInput->setReadOnly(true); - - systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" }); - connectAction->setEnabled(false); - break; - case VpnStatus::connected: - ui->statusLabel->setText("Connected"); - ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;"); - ui->connectButton->setText("Disconnect"); - ui->connectButton->setDisabled(false); - ui->portalInput->setReadOnly(true); - - systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" }); - connectAction->setEnabled(true); - connectAction->setText("Disconnect"); - break; - default: - break; - } + populateGatewayMenu(); } -void GPClient::onVPNConnected() +void GPClient::initSystemTrayIcon() { - updateConnectionStatus(VpnStatus::connected); -} + systemTrayIcon = new QSystemTrayIcon(this); + contextMenu = new QMenu("GlobalProtect", this); -void GPClient::onVPNDisconnected() -{ - updateConnectionStatus(VpnStatus::disconnected); -} + gatewaySwitchMenu = new QMenu("Switch Gateway", this); + gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup")); + populateGatewayMenu(); -void GPClient::onVPNLogAvailable(QString log) -{ - PLOGI << log; -} + systemTrayIcon->setIcon(QIcon(":/images/not_connected.png")); + systemTrayIcon->setToolTip("GlobalProtect"); + systemTrayIcon->setContextMenu(contextMenu); -void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) -{ - switch (reason) { - case QSystemTrayIcon::Trigger: - case QSystemTrayIcon::DoubleClick: - this->activiate(); - break; - default: - break; - } -} + connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated); + connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged); -void GPClient::activiate() -{ - activateWindow(); - showNormal(); -} + openAction = contextMenu->addAction(QIcon::fromTheme("window-new"), "Open", this, &GPClient::activiate); + connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect); + contextMenu->addMenu(gatewaySwitchMenu); + contextMenu->addSeparator(); + clearAction = contextMenu->addAction(QIcon::fromTheme("edit-clear"), "Reset Settings", this, &GPClient::clearSettings); + quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit); -QString GPClient::portal() const -{ - const QString input = ui->portalInput->text().trimmed(); - - if (input.startsWith("http")) { - return QUrl(input).authority(); - } - return input; + systemTrayIcon->show(); } void GPClient::initVpnStatus() { @@ -161,11 +95,115 @@ void GPClient::initVpnStatus() { } } +void GPClient::populateGatewayMenu() +{ + const QList gateways = allGateways(); + gatewaySwitchMenu->clear(); + + if (gateways.isEmpty()) { + gatewaySwitchMenu->addAction("")->setData(-1); + return; + } + + const QString currentGatewayName = currentGateway().name(); + for (int i = 0; i < gateways.length(); i++) { + const GPGateway g = gateways.at(i); + QString iconImage = ":/images/radio_unselected.png"; + if (g.name() == currentGatewayName) { + iconImage = ":/images/radio_selected.png"; + } + gatewaySwitchMenu->addAction(QIcon(iconImage), g.name())->setData(i); + } +} + +void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) +{ + switch (status) { + case VpnStatus::disconnected: + ui->statusLabel->setText("Not Connected"); + ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;"); + ui->connectButton->setText("Connect"); + ui->connectButton->setDisabled(false); + ui->portalInput->setReadOnly(false); + + systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" }); + connectAction->setEnabled(true); + connectAction->setText("Connect"); + gatewaySwitchMenu->setEnabled(true); + clearAction->setEnabled(true); + break; + case VpnStatus::pending: + ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;"); + ui->connectButton->setDisabled(true); + ui->portalInput->setReadOnly(true); + + systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" }); + connectAction->setEnabled(false); + gatewaySwitchMenu->setEnabled(false); + clearAction->setEnabled(false); + break; + case VpnStatus::connected: + ui->statusLabel->setText("Connected"); + ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;"); + ui->connectButton->setText("Disconnect"); + ui->connectButton->setDisabled(false); + ui->portalInput->setReadOnly(true); + + systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" }); + connectAction->setEnabled(true); + connectAction->setText("Disconnect"); + gatewaySwitchMenu->setEnabled(true); + clearAction->setEnabled(false); + break; + default: + break; + } +} + +void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) +{ + switch (reason) { + case QSystemTrayIcon::Trigger: + case QSystemTrayIcon::DoubleClick: + this->activiate(); + break; + default: + break; + } +} + +void GPClient::onGatewayChanged(QAction *action) +{ + const int index = action->data().toInt(); + + if (index == -1) { + return; + } + + const GPGateway g = allGateways().at(index); + + // If the selected gateway is the same as the current gateway + if (g.name() == currentGateway().name()) { + return; + } + + setCurrentGateway(g); + + if (connected()) { + ui->statusLabel->setText("Switching Gateway..."); + ui->connectButton->setEnabled(false); + + vpn->disconnect(); + isSwitchingGateway = true; + } +} + void GPClient::doConnect() { const QString btnText = ui->connectButton->text(); const QString portal = this->portal(); + // Display the main window if portal is empty if (portal.isEmpty()) { activiate(); return; @@ -173,11 +211,15 @@ void GPClient::doConnect() if (btnText.endsWith("Connect")) { settings::save("portal", portal); - ui->statusLabel->setText("Authenticating..."); - updateConnectionStatus(VpnStatus::pending); - // Perform the portal login - portalLogin(portal); + // Login to the previously saved gateway + if (!currentGateway().name().isEmpty()) { + isQuickConnect = true; + gatewayLogin(); + } else { + // Perform the portal login + portalLogin(); + } } else { ui->statusLabel->setText("Disconnecting..."); updateConnectionStatus(VpnStatus::pending); @@ -187,9 +229,9 @@ void GPClient::doConnect() } // Login to the portal interface to get the portal config and preferred gateway -void GPClient::portalLogin(const QString& portal) +void GPClient::portalLogin() { - PortalAuthenticator *portalAuth = new PortalAuthenticator(portal); + PortalAuthenticator *portalAuth = new PortalAuthenticator(portal()); connect(portalAuth, &PortalAuthenticator::success, this, &GPClient::onPortalSuccess); // Prelogin failed on the portal interface, try to treat the portal as a gateway interface @@ -197,13 +239,16 @@ void GPClient::portalLogin(const QString& portal) // Portal login failed connect(portalAuth, &PortalAuthenticator::fail, this, &GPClient::onPortalFail); + ui->statusLabel->setText("Authenticating..."); + updateConnectionStatus(VpnStatus::pending); portalAuth->authenticate(); } -void GPClient::onPortalSuccess(const PortalConfigResponse &portalConfig, const GPGateway &gateway) +void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const GPGateway gateway, QList allGateways) { this->portalConfig = portalConfig; - this->gateway = gateway; + setAllGateways(allGateways); + setCurrentGateway(gateway); gatewayLogin(); } @@ -212,8 +257,17 @@ void GPClient::onPortalPreloginFail() { PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface..."; - // Set the gateway address to portal input - gateway.setAddress(portal()); + // Treat the portal input as the gateway address + GPGateway g; + g.setName(portal()); + g.setAddress(portal()); + + QList gateways; + gateways.append(g); + + setAllGateways(gateways); + setCurrentGateway(g); + gatewayLogin(); } @@ -227,36 +281,126 @@ void GPClient::onPortalFail(const QString &msg) } // Login to the gateway -void GPClient::gatewayLogin() const +void GPClient::gatewayLogin() { - GatewayAuthenticator *gatewayAuth = new GatewayAuthenticator(gateway.address(), portalConfig); + GatewayAuthenticator *gatewayAuth = new GatewayAuthenticator(currentGateway().address(), portalConfig); connect(gatewayAuth, &GatewayAuthenticator::success, this, &GPClient::onGatewaySuccess); connect(gatewayAuth, &GatewayAuthenticator::fail, this, &GPClient::onGatewayFail); + ui->statusLabel->setText("Authenticating..."); + updateConnectionStatus(VpnStatus::pending); gatewayAuth->authenticate(); } +void GPClient::onGatewaySuccess(const QString &authCookie) +{ + PLOGI << "Gateway login succeeded, got the cookie " << authCookie; + + isQuickConnect = false; + vpn->connect(currentGateway().address(), portalConfig.username(), authCookie); + ui->statusLabel->setText("Connecting..."); + updateConnectionStatus(VpnStatus::pending); +} + +void GPClient::onGatewayFail(const QString &msg) +{ + // If the quick connect on gateway failed, perform the portal login + if (isQuickConnect && !msg.isEmpty()) { + isQuickConnect = false; + portalLogin(); + return; + } + + if (!msg.isEmpty()) { + openMessageBox("Gateway authentication failed.", msg); + } + + updateConnectionStatus(VpnStatus::disconnected); +} + +void GPClient::activiate() +{ + activateWindow(); + showNormal(); +} + +QString GPClient::portal() const +{ + const QString input = ui->portalInput->text().trimmed(); + + if (input.startsWith("http")) { + return QUrl(input).authority(); + } + return input; +} + +bool GPClient::connected() const +{ + const QString statusText = ui->statusLabel->text(); + return statusText.contains("Connected") && !statusText.contains("Not"); +} + + +QList GPClient::allGateways() const +{ + const QString gatewaysJson = settings::get(portal() + "_gateways").toString(); + return GPGateway::fromJson(gatewaysJson); +} + +void GPClient::setAllGateways(QList gateways) +{ + settings::save(portal() + "_gateways", GPGateway::serialize(gateways)); + populateGatewayMenu(); +} + +GPGateway GPClient::currentGateway() const +{ + const QString selectedGateway = settings::get(portal() + "_selectedGateway").toString(); + + for (auto g : allGateways()) { + if (g.name() == selectedGateway) { + return g; + } + } + return GPGateway{}; +} + +void GPClient::setCurrentGateway(const GPGateway gateway) +{ + settings::save(portal() + "_selectedGateway", gateway.name()); + populateGatewayMenu(); +} + +void GPClient::clearSettings() +{ + settings::clear(); + populateGatewayMenu(); + ui->portalInput->clear(); +} + void GPClient::quit() { vpn->disconnect(); QApplication::quit(); } -void GPClient::onGatewaySuccess(const QString &authCookie) +void GPClient::onVPNConnected() { - PLOGI << "Gateway login succeeded, got the cookie " << authCookie; - - vpn->connect(gateway.address(), portalConfig.username(), authCookie); - ui->statusLabel->setText("Connecting..."); - updateConnectionStatus(VpnStatus::pending); + updateConnectionStatus(VpnStatus::connected); } -void GPClient::onGatewayFail(const QString &msg) +void GPClient::onVPNDisconnected() { - if (!msg.isEmpty()) { - openMessageBox("Portal authentication failed.", msg); - } - updateConnectionStatus(VpnStatus::disconnected); + + if (isSwitchingGateway) { + gatewayLogin(); + isSwitchingGateway = false; + } +} + +void GPClient::onVPNLogAvailable(QString log) +{ + PLOGI << log; } diff --git a/GPClient/gpclient.h b/GPClient/gpclient.h index f03a50f..71c08a1 100644 --- a/GPClient/gpclient.h +++ b/GPClient/gpclient.h @@ -19,15 +19,21 @@ class GPClient : public QMainWindow public: GPClient(QWidget *parent = nullptr); ~GPClient(); + void activiate(); private slots: void on_connectButton_clicked(); void on_portalInput_returnPressed(); + void on_portalInput_editingFinished(); - void onPortalSuccess(const PortalConfigResponse &portalConfig, const GPGateway &gateway); + void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason); + void onGatewayChanged(QAction *action); + + void onPortalSuccess(const PortalConfigResponse portalConfig, const GPGateway gateway, QList allGateways); void onPortalPreloginFail(); void onPortalFail(const QString &msg); + void onGatewaySuccess(const QString &authCookie); void onGatewayFail(const QString &msg); @@ -35,8 +41,6 @@ private slots: void onVPNDisconnected(); void onVPNLogAvailable(QString log); - void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason); - private: enum class VpnStatus { @@ -52,20 +56,34 @@ private: QMenu *contextMenu; QAction *openAction; QAction *connectAction; + + QMenu *gatewaySwitchMenu; + QAction *clearAction; QAction *quitAction; - GPGateway gateway; + bool isQuickConnect { false }; + bool isSwitchingGateway { false }; PortalConfigResponse portalConfig; - QString portal() const; - + void initSystemTrayIcon(); void initVpnStatus(); - void doConnect(); + void populateGatewayMenu(); void updateConnectionStatus(const VpnStatus &status); - void portalLogin(const QString& portal); - void gatewayLogin() const; + void doConnect(); + void portalLogin(); + void gatewayLogin(); + QString portal() const; + bool connected() const; + + QList allGateways() const; + void setAllGateways(QList gateways); + + GPGateway currentGateway() const; + void setCurrentGateway(const GPGateway gateway); + + void clearSettings(); void quit(); }; #endif // GPCLIENT_H diff --git a/GPClient/gpgateway.cpp b/GPClient/gpgateway.cpp index f150fca..5725925 100644 --- a/GPClient/gpgateway.cpp +++ b/GPClient/gpgateway.cpp @@ -1,5 +1,9 @@ #include "gpgateway.h" +#include +#include +#include + GPGateway::GPGateway() { } @@ -36,3 +40,58 @@ int GPGateway::priorityOf(QString ruleName) const } return 0; } + +QJsonObject GPGateway::toJsonObject() const +{ + QJsonObject obj; + obj.insert("name", name()); + obj.insert("address", address()); + + return obj; +} + +QString GPGateway::toString() const +{ + QJsonDocument jsonDoc{ toJsonObject() }; + return QString::fromUtf8(jsonDoc.toJson()); +} + +QString GPGateway::serialize(QList &gateways) +{ + QJsonArray arr; + + for (auto g : gateways) { + arr.append(g.toJsonObject()); + } + + QJsonDocument jsonDoc{ arr }; + return QString::fromUtf8(jsonDoc.toJson()); +} + +QList GPGateway::fromJson(const QString &jsonString) +{ + QList gateways; + + if (jsonString.isEmpty()) { + return gateways; + } + + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + + for (auto item : jsonDoc.array()) { + GPGateway g = GPGateway::fromJsonObject(item.toObject()); + gateways.append(g); + } + + return gateways; +} + +GPGateway GPGateway::fromJsonObject(const QJsonObject &jsonObj) +{ + GPGateway g; + + g.setName(jsonObj.value("name").toString()); + g.setAddress(jsonObj.value("address").toString()); + + return g; +} diff --git a/GPClient/gpgateway.h b/GPClient/gpgateway.h index af0564b..0bfb449 100644 --- a/GPClient/gpgateway.h +++ b/GPClient/gpgateway.h @@ -3,6 +3,7 @@ #include #include +#include class GPGateway { @@ -16,6 +17,12 @@ public: void setAddress(const QString &address); void setPriorityRules(const QMap &priorityRules); int priorityOf(QString ruleName) const; + QJsonObject toJsonObject() const; + QString toString() const; + + static QString serialize(QList &gateways); + static QList fromJson(const QString &jsonString); + static GPGateway fromJsonObject(const QJsonObject &jsonObj); private: QString _name; diff --git a/GPClient/gphelper.cpp b/GPClient/gphelper.cpp index f9b74d2..53053e3 100644 --- a/GPClient/gphelper.cpp +++ b/GPClient/gphelper.cpp @@ -21,27 +21,11 @@ QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params) return networkManager->post(request, params); } -SAMLLoginWindow* gpclient::helper::samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl) +GPGateway gpclient::helper::filterPreferredGateway(QList gateways, const QString ruleName) { - SAMLLoginWindow *loginWindow = new SAMLLoginWindow; + GPGateway gateway = gateways.first(); - if (samlMethod == "POST") { - loginWindow->login(preloginUrl, samlRequest); - } else if (samlMethod == "REDIRECT") { - loginWindow->login(samlRequest); - } else { - PLOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; - return nullptr; - } - - return loginWindow; -} - -GPGateway gpclient::helper::filterPreferredGateway(QList *gateways, const QString ruleName) -{ - GPGateway gateway = gateways->first(); - - for (GPGateway g : *gateways) { + for (GPGateway g : gateways) { if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) { gateway = g; } @@ -76,7 +60,7 @@ QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml) void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText) { QMessageBox msgBox; - msgBox.setWindowTitle("GlobalProtect"); + msgBox.setWindowTitle("Notice"); msgBox.setText(message); msgBox.setFixedWidth(500); msgBox.setStyleSheet("QLabel{min-width: 250px}"); @@ -117,3 +101,8 @@ void gpclient::helper::settings::save(const QString &key, const QVariant &value) { _settings->setValue(key, value); } + +void gpclient::helper::settings::clear() +{ + _settings->clear(); +} diff --git a/GPClient/gphelper.h b/GPClient/gphelper.h index dfc8b2e..9d9664e 100644 --- a/GPClient/gphelper.h +++ b/GPClient/gphelper.h @@ -20,9 +20,7 @@ namespace gpclient { QNetworkReply* createRequest(QString url, QByteArray params = nullptr); - SAMLLoginWindow *samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl); - - GPGateway filterPreferredGateway(QList *gateways, const QString ruleName); + GPGateway filterPreferredGateway(QList gateways, const QString ruleName); QUrlQuery parseGatewayResponse(const QByteArray& xml); @@ -36,6 +34,7 @@ namespace gpclient { QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); void save(const QString &key, const QVariant &value); + void clear(); } } } diff --git a/GPClient/portalauthenticator.cpp b/GPClient/portalauthenticator.cpp index 452cb33..6de27f8 100644 --- a/GPClient/portalauthenticator.cpp +++ b/GPClient/portalauthenticator.cpp @@ -113,24 +113,27 @@ void PortalAuthenticator::samlAuth() { PLOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod(); - SAMLLoginWindow *loginWindow = samlLogin(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); - - if (!loginWindow) { - openMessageBox("SAML Login failed for portal"); - return; - } + SAMLLoginWindow *loginWindow = new SAMLLoginWindow; connect(loginWindow, &SAMLLoginWindow::success, this, &PortalAuthenticator::onSAMLLoginSuccess); + connect(loginWindow, &SAMLLoginWindow::fail, this, &PortalAuthenticator::onSAMLLoginFail); connect(loginWindow, &SAMLLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected); + + loginWindow->login(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); } -void PortalAuthenticator::onSAMLLoginSuccess(const QMap &samlResult) +void PortalAuthenticator::onSAMLLoginSuccess(const QMap samlResult) { PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie"); fetchConfig(samlResult.value("username"), "", samlResult.value("preloginCookie")); } +void PortalAuthenticator::onSAMLLoginFail(const QString msg) +{ + emitFail(msg); +} + void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie) { LoginParams params; @@ -146,7 +149,6 @@ void PortalAuthenticator::fetchConfig(QString username, QString password, QStrin PLOGI << "Fetching the portal config from " << configUrl << " for user: " << username; QNetworkReply *reply = createRequest(configUrl, params.toUtf8()); - connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); } @@ -185,7 +187,7 @@ void PortalAuthenticator::onFetchConfigFinished() normalLoginWindow->close(); } - emit success(response, filterPreferredGateway(response.allGateways(), preloginResponse.region())); + emit success(response, filterPreferredGateway(response.allGateways(), preloginResponse.region()), response.allGateways()); } void PortalAuthenticator::emitFail(const QString& msg) diff --git a/GPClient/portalauthenticator.h b/GPClient/portalauthenticator.h index 4f3b12f..073daf3 100644 --- a/GPClient/portalauthenticator.h +++ b/GPClient/portalauthenticator.h @@ -18,7 +18,7 @@ public: void authenticate(); signals: - void success(const PortalConfigResponse&, const GPGateway&); + void success(const PortalConfigResponse, const GPGateway, QList allGateways); void fail(const QString& msg); void preloginFailed(const QString& msg); @@ -27,7 +27,8 @@ private slots: void onPerformNormalLogin(const QString &username, const QString &password); void onLoginWindowRejected(); void onLoginWindowFinished(); - void onSAMLLoginSuccess(const QMap &samlResult); + void onSAMLLoginSuccess(const QMap samlResult); + void onSAMLLoginFail(const QString msg); void onFetchConfigFinished(); private: diff --git a/GPClient/portalconfigresponse.cpp b/GPClient/portalconfigresponse.cpp index 1bf4a2d..01f4f5e 100644 --- a/GPClient/portalconfigresponse.cpp +++ b/GPClient/portalconfigresponse.cpp @@ -8,13 +8,11 @@ QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserau QString PortalConfigResponse::xmlGateways = "gateways"; PortalConfigResponse::PortalConfigResponse() - : _gateways(new QList) { } PortalConfigResponse::~PortalConfigResponse() { - delete _gateways; } PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml) @@ -33,7 +31,7 @@ PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml) } else if (name == xmlPrelogonUserAuthCookie) { response.setPrelogonUserAuthCookie(xmlReader.readElementText()); } else if (name == xmlGateways) { - parseGateways(xmlReader, response.allGateways()); + response.setAllGateways(parseGateways(xmlReader)); } } @@ -55,20 +53,23 @@ QString PortalConfigResponse::password() const return _password; } -void PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader, QList *gateways) +QList PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader) { + QList gateways; + while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) { xmlReader.readNext(); // Parse the gateways -> external -> list -> entry if (xmlReader.name() == "entry" && xmlReader.isStartElement()) { - GPGateway gateway; + GPGateway g; QString address = xmlReader.attributes().value("name").toString(); - gateway.setAddress(address); - gateway.setPriorityRules(parsePriorityRules(xmlReader)); - gateway.setName(parseGatewayName(xmlReader)); - gateways->append(gateway); + g.setAddress(address); + g.setPriorityRules(parsePriorityRules(xmlReader)); + g.setName(parseGatewayName(xmlReader)); + gateways.append(g); } } + return gateways; } QMap PortalConfigResponse::parsePriorityRules(QXmlStreamReader &xmlReader) @@ -112,11 +113,16 @@ QString PortalConfigResponse::prelogonUserAuthCookie() const return _prelogonAuthCookie; } -QList* PortalConfigResponse::allGateways() +QList PortalConfigResponse::allGateways() { return _gateways; } +void PortalConfigResponse::setAllGateways(QList gateways) +{ + _gateways = gateways; +} + void PortalConfigResponse::setRawResponse(const QByteArray &response) { _rawResponse = response; diff --git a/GPClient/portalconfigresponse.h b/GPClient/portalconfigresponse.h index ac5dd6c..df74063 100644 --- a/GPClient/portalconfigresponse.h +++ b/GPClient/portalconfigresponse.h @@ -20,7 +20,8 @@ public: QString password() const; QString userAuthCookie() const; QString prelogonUserAuthCookie() const; - QList* allGateways(); + QList allGateways(); + void setAllGateways(QList gateways); void setUsername(const QString& username); void setPassword(const QString& password); @@ -36,13 +37,13 @@ private: QString _userAuthCookie; QString _prelogonAuthCookie; - QList *_gateways; + QList _gateways; void setRawResponse(const QByteArray& response); void setUserAuthCookie(const QString& cookie); void setPrelogonUserAuthCookie(const QString& cookie); - static void parseGateways(QXmlStreamReader &xmlReader, QList *gateways); + static QList parseGateways(QXmlStreamReader &xmlReader); static QMap parsePriorityRules(QXmlStreamReader &xmlReader); static QString parseGatewayName(QXmlStreamReader &xmlReader); }; diff --git a/GPClient/radio_selected.png b/GPClient/radio_selected.png new file mode 100644 index 0000000..adc3d69 Binary files /dev/null and b/GPClient/radio_selected.png differ diff --git a/GPClient/radio_unselected.png b/GPClient/radio_unselected.png new file mode 100644 index 0000000..4542f73 Binary files /dev/null and b/GPClient/radio_unselected.png differ diff --git a/GPClient/resources.qrc b/GPClient/resources.qrc index 378d30b..88130b8 100644 --- a/GPClient/resources.qrc +++ b/GPClient/resources.qrc @@ -4,5 +4,7 @@ connected.png pending.png not_connected.png + radio_unselected.png + radio_selected.png diff --git a/GPClient/samlloginwindow.cpp b/GPClient/samlloginwindow.cpp index 97f9c98..2d11dd6 100644 --- a/GPClient/samlloginwindow.cpp +++ b/GPClient/samlloginwindow.cpp @@ -3,16 +3,17 @@ #include #include #include +#include SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) : QDialog(parent) + , webView(new EnhancedWebView(this)) { setWindowTitle("GlobalProtect SAML Login"); setModal(true); resize(700, 550); QVBoxLayout *verticalLayout = new QVBoxLayout(this); - webView = new EnhancedWebView(this); webView->setUrl(QUrl("about:blank")); // webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); verticalLayout->addWidget(webView); @@ -33,12 +34,15 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event) reject(); } -void SAMLLoginWindow::login(QString url, QString html) +void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl) { - if (html.isEmpty()) { - webView->load(QUrl(url)); + if (samlMethod == "POST") { + webView->setHtml(samlRequest, preloingUrl); + } else if (samlMethod == "REDIRECT") { + webView->load(samlRequest); } else { - webView->setHtml(html, url); + PLOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; + emit fail("Unknown saml-auth-method, got " + samlMethod); } } diff --git a/GPClient/samlloginwindow.h b/GPClient/samlloginwindow.h index ec36b12..09d8a2e 100644 --- a/GPClient/samlloginwindow.h +++ b/GPClient/samlloginwindow.h @@ -15,10 +15,11 @@ public: explicit SAMLLoginWindow(QWidget *parent = nullptr); ~SAMLLoginWindow(); - void login(QString url, QString html = ""); + void login(const QString samlMethod, const QString samlRequest, const QString preloingUrl); signals: void success(QMap samlResult); + void fail(const QString msg); private slots: void onResponseReceived(QJsonObject params); diff --git a/GPService/gpservice_adaptor.cpp b/GPService/gpservice_adaptor.cpp deleted file mode 100644 index d23923a..0000000 --- a/GPService/gpservice_adaptor.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -i gpservice_adaptor.h -a :gpservice_adaptor.cpp gpservice.xml - * - * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. - * - * This is an auto-generated file. - * Do not edit! All changes made to it will be lost. - */ - -#include "gpservice_adaptor.h" -#include -#include -#include -#include -#include -#include -#include - -/* - * Implementation of adaptor class GPServiceAdaptor - */ - -GPServiceAdaptor::GPServiceAdaptor(QObject *parent) - : QDBusAbstractAdaptor(parent) -{ - // constructor - setAutoRelaySignals(true); -} - -GPServiceAdaptor::~GPServiceAdaptor() -{ - // destructor -} - -void GPServiceAdaptor::connect(const QString &server, const QString &username, const QString &passwd) -{ - // handle method call com.yuezk.qt.GPService.connect - QMetaObject::invokeMethod(parent(), "connect", Q_ARG(QString, server), Q_ARG(QString, username), Q_ARG(QString, passwd)); -} - -void GPServiceAdaptor::disconnect() -{ - // handle method call com.yuezk.qt.GPService.disconnect - QMetaObject::invokeMethod(parent(), "disconnect"); -} - -int GPServiceAdaptor::status() -{ - // handle method call com.yuezk.qt.GPService.status - int out0; - QMetaObject::invokeMethod(parent(), "status", Q_RETURN_ARG(int, out0)); - return out0; -} - diff --git a/GPService/gpservice_adaptor.h b/GPService/gpservice_adaptor.h deleted file mode 100644 index c823e90..0000000 --- a/GPService/gpservice_adaptor.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -a gpservice_adaptor.h: gpservice.xml - * - * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. - * - * This is an auto-generated file. - * This file may have been hand-edited. Look for HAND-EDIT comments - * before re-generating it. - */ - -#ifndef GPSERVICE_ADAPTOR_H -#define GPSERVICE_ADAPTOR_H - -#include -#include -QT_BEGIN_NAMESPACE -class QByteArray; -template class QList; -template class QMap; -class QString; -class QStringList; -class QVariant; -QT_END_NAMESPACE - -/* - * Adaptor class for interface com.yuezk.qt.GPService - */ -class GPServiceAdaptor: public QDBusAbstractAdaptor -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") - Q_CLASSINFO("D-Bus Introspection", "" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" - "") -public: - GPServiceAdaptor(QObject *parent); - virtual ~GPServiceAdaptor(); - -public: // PROPERTIES -public Q_SLOTS: // METHODS - void connect(const QString &server, const QString &username, const QString &passwd); - void disconnect(); - int status(); -Q_SIGNALS: // SIGNALS - void connected(); - void disconnected(); - void logAvailable(const QString &log); -}; - -#endif diff --git a/README.md b/README.md index 3d6a6dc..5e7fe73 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Q ## Features -- Supports both SAML and non-SAML authentication modes. -- Supports automatically select the preferred gateway from the multiple gateways. - Similar user experience as the offical client in macOS. +- Supports both SAML and non-SAML authentication modes. +- Supports automatically selecting the preferred gateway from the multiple gateways. +- Supports switching gateway manually. ## Prerequisites