Add support to switch gateway

This commit is contained in:
Kevin Yue 2020-05-24 22:38:54 +08:00
parent e22bb8e1b7
commit 599ff3668f
20 changed files with 422 additions and 305 deletions

View File

@ -140,18 +140,16 @@ void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QSt
{ {
PLOGI << "Trying to perform SAML login with saml-method " << samlMethod; PLOGI << "Trying to perform SAML login with saml-method " << samlMethod;
SAMLLoginWindow *loginWindow = samlLogin(samlMethod, samlRequest, preloginUrl); SAMLLoginWindow *loginWindow = new SAMLLoginWindow;
if (!loginWindow) { connect(loginWindow, &SAMLLoginWindow::success, this, &GatewayAuthenticator::onSAMLLoginSuccess);
openMessageBox("SAML Login failed for gateway"); connect(loginWindow, &SAMLLoginWindow::fail, this, &GatewayAuthenticator::onSAMLLoginFail);
return;
}
connect(loginWindow, &SAMLLoginWindow::success, this, &GatewayAuthenticator::onSAMLLoginFinished);
connect(loginWindow, &SAMLLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected); connect(loginWindow, &SAMLLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected);
loginWindow->login(samlMethod, samlRequest, preloginUrl);
} }
void GatewayAuthenticator::onSAMLLoginFinished(const QMap<QString, QString> &samlResult) void GatewayAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> &samlResult)
{ {
PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie"); PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie");
@ -161,3 +159,8 @@ void GatewayAuthenticator::onSAMLLoginFinished(const QMap<QString, QString> &sam
login(params); login(params);
} }
void GatewayAuthenticator::onSAMLLoginFail(const QString msg)
{
emit fail(msg);
}

View File

@ -25,7 +25,8 @@ private slots:
void onPerformNormalLogin(const QString &username, const QString &password); void onPerformNormalLogin(const QString &username, const QString &password);
void onLoginWindowRejected(); void onLoginWindowRejected();
void onLoginWindowFinished(); void onLoginWindowFinished();
void onSAMLLoginFinished(const QMap<QString, QString> &samlResult); void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult);
void onSAMLLoginFail(const QString msg);
private: private:
QString gateway; QString gateway;
@ -34,7 +35,7 @@ private:
const PortalConfigResponse& portalConfig; const PortalConfigResponse& portalConfig;
NormalLoginWindow *normalLoginWindow{nullptr}; NormalLoginWindow *normalLoginWindow{ nullptr };
void login(const LoginParams& params); void login(const LoginParams& params);
void doAuth(); void doAuth();

View File

@ -12,8 +12,6 @@ using namespace gpclient::helper;
GPClient::GPClient(QWidget *parent) GPClient::GPClient(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, ui(new Ui::GPClient) , ui(new Ui::GPClient)
, systemTrayIcon(new QSystemTrayIcon(parent))
, contextMenu(new QMenu("GlobalProtect", parent))
{ {
ui->setupUi(this); ui->setupUi(this);
setWindowTitle("GlobalProtect"); 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::disconnected, this, &GPClient::onVPNDisconnected);
connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable); connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable);
connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated);
// Initiallize the context menu of system tray. // Initiallize the context menu of system tray.
openAction = contextMenu->addAction(QIcon::fromTheme("system-run"), "Open", this, &GPClient::activiate); initSystemTrayIcon();
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");
initVpnStatus(); initVpnStatus();
systemTrayIcon->show();
} }
GPClient::~GPClient() GPClient::~GPClient()
{ {
delete ui; delete ui;
delete vpn; delete vpn;
delete systemTrayIcon;
delete openAction;
delete connectAction;
delete quitAction;
delete contextMenu;
} }
void GPClient::on_connectButton_clicked() void GPClient::on_connectButton_clicked()
@ -64,85 +48,35 @@ void GPClient::on_portalInput_returnPressed()
doConnect(); doConnect();
} }
void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) void GPClient::on_portalInput_editingFinished()
{ {
switch (status) { populateGatewayMenu();
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;
}
} }
void GPClient::onVPNConnected() void GPClient::initSystemTrayIcon()
{ {
updateConnectionStatus(VpnStatus::connected); systemTrayIcon = new QSystemTrayIcon(this);
} contextMenu = new QMenu("GlobalProtect", this);
void GPClient::onVPNDisconnected() gatewaySwitchMenu = new QMenu("Switch Gateway", this);
{ gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup"));
updateConnectionStatus(VpnStatus::disconnected); populateGatewayMenu();
}
void GPClient::onVPNLogAvailable(QString log) systemTrayIcon->setIcon(QIcon(":/images/not_connected.png"));
{ systemTrayIcon->setToolTip("GlobalProtect");
PLOGI << log; systemTrayIcon->setContextMenu(contextMenu);
}
void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated);
{ connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged);
switch (reason) {
case QSystemTrayIcon::Trigger:
case QSystemTrayIcon::DoubleClick:
this->activiate();
break;
default:
break;
}
}
void GPClient::activiate() openAction = contextMenu->addAction(QIcon::fromTheme("window-new"), "Open", this, &GPClient::activiate);
{ connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect);
activateWindow(); contextMenu->addMenu(gatewaySwitchMenu);
showNormal(); 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 systemTrayIcon->show();
{
const QString input = ui->portalInput->text().trimmed();
if (input.startsWith("http")) {
return QUrl(input).authority();
}
return input;
} }
void GPClient::initVpnStatus() { void GPClient::initVpnStatus() {
@ -161,11 +95,115 @@ void GPClient::initVpnStatus() {
} }
} }
void GPClient::populateGatewayMenu()
{
const QList<GPGateway> gateways = allGateways();
gatewaySwitchMenu->clear();
if (gateways.isEmpty()) {
gatewaySwitchMenu->addAction("<None>")->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() void GPClient::doConnect()
{ {
const QString btnText = ui->connectButton->text(); const QString btnText = ui->connectButton->text();
const QString portal = this->portal(); const QString portal = this->portal();
// Display the main window if portal is empty
if (portal.isEmpty()) { if (portal.isEmpty()) {
activiate(); activiate();
return; return;
@ -173,11 +211,15 @@ void GPClient::doConnect()
if (btnText.endsWith("Connect")) { if (btnText.endsWith("Connect")) {
settings::save("portal", portal); settings::save("portal", portal);
ui->statusLabel->setText("Authenticating...");
updateConnectionStatus(VpnStatus::pending);
// Perform the portal login // Login to the previously saved gateway
portalLogin(portal); if (!currentGateway().name().isEmpty()) {
isQuickConnect = true;
gatewayLogin();
} else {
// Perform the portal login
portalLogin();
}
} else { } else {
ui->statusLabel->setText("Disconnecting..."); ui->statusLabel->setText("Disconnecting...");
updateConnectionStatus(VpnStatus::pending); updateConnectionStatus(VpnStatus::pending);
@ -187,9 +229,9 @@ 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(const QString& portal) void GPClient::portalLogin()
{ {
PortalAuthenticator *portalAuth = new PortalAuthenticator(portal); PortalAuthenticator *portalAuth = new PortalAuthenticator(portal());
connect(portalAuth, &PortalAuthenticator::success, this, &GPClient::onPortalSuccess); connect(portalAuth, &PortalAuthenticator::success, this, &GPClient::onPortalSuccess);
// Prelogin failed on the portal interface, try to treat the portal as a gateway interface // 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 // Portal login failed
connect(portalAuth, &PortalAuthenticator::fail, this, &GPClient::onPortalFail); connect(portalAuth, &PortalAuthenticator::fail, this, &GPClient::onPortalFail);
ui->statusLabel->setText("Authenticating...");
updateConnectionStatus(VpnStatus::pending);
portalAuth->authenticate(); portalAuth->authenticate();
} }
void GPClient::onPortalSuccess(const PortalConfigResponse &portalConfig, const GPGateway &gateway) void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const GPGateway gateway, QList<GPGateway> allGateways)
{ {
this->portalConfig = portalConfig; this->portalConfig = portalConfig;
this->gateway = gateway; setAllGateways(allGateways);
setCurrentGateway(gateway);
gatewayLogin(); gatewayLogin();
} }
@ -212,8 +257,17 @@ void GPClient::onPortalPreloginFail()
{ {
PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface..."; PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface...";
// Set the gateway address to portal input // Treat the portal input as the gateway address
gateway.setAddress(portal()); GPGateway g;
g.setName(portal());
g.setAddress(portal());
QList<GPGateway> gateways;
gateways.append(g);
setAllGateways(gateways);
setCurrentGateway(g);
gatewayLogin(); gatewayLogin();
} }
@ -227,36 +281,126 @@ void GPClient::onPortalFail(const QString &msg)
} }
// Login to the gateway // 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::success, this, &GPClient::onGatewaySuccess);
connect(gatewayAuth, &GatewayAuthenticator::fail, this, &GPClient::onGatewayFail); connect(gatewayAuth, &GatewayAuthenticator::fail, this, &GPClient::onGatewayFail);
ui->statusLabel->setText("Authenticating...");
updateConnectionStatus(VpnStatus::pending);
gatewayAuth->authenticate(); 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<GPGateway> GPClient::allGateways() const
{
const QString gatewaysJson = settings::get(portal() + "_gateways").toString();
return GPGateway::fromJson(gatewaysJson);
}
void GPClient::setAllGateways(QList<GPGateway> 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() void GPClient::quit()
{ {
vpn->disconnect(); vpn->disconnect();
QApplication::quit(); QApplication::quit();
} }
void GPClient::onGatewaySuccess(const QString &authCookie) void GPClient::onVPNConnected()
{ {
PLOGI << "Gateway login succeeded, got the cookie " << authCookie; updateConnectionStatus(VpnStatus::connected);
vpn->connect(gateway.address(), portalConfig.username(), authCookie);
ui->statusLabel->setText("Connecting...");
updateConnectionStatus(VpnStatus::pending);
} }
void GPClient::onGatewayFail(const QString &msg) void GPClient::onVPNDisconnected()
{ {
if (!msg.isEmpty()) {
openMessageBox("Portal authentication failed.", msg);
}
updateConnectionStatus(VpnStatus::disconnected); updateConnectionStatus(VpnStatus::disconnected);
if (isSwitchingGateway) {
gatewayLogin();
isSwitchingGateway = false;
}
}
void GPClient::onVPNLogAvailable(QString log)
{
PLOGI << log;
} }

View File

@ -19,15 +19,21 @@ class GPClient : public QMainWindow
public: public:
GPClient(QWidget *parent = nullptr); GPClient(QWidget *parent = nullptr);
~GPClient(); ~GPClient();
void activiate(); void activiate();
private slots: private slots:
void on_connectButton_clicked(); void on_connectButton_clicked();
void on_portalInput_returnPressed(); 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<GPGateway> allGateways);
void onPortalPreloginFail(); void onPortalPreloginFail();
void onPortalFail(const QString &msg); void onPortalFail(const QString &msg);
void onGatewaySuccess(const QString &authCookie); void onGatewaySuccess(const QString &authCookie);
void onGatewayFail(const QString &msg); void onGatewayFail(const QString &msg);
@ -35,8 +41,6 @@ private slots:
void onVPNDisconnected(); void onVPNDisconnected();
void onVPNLogAvailable(QString log); void onVPNLogAvailable(QString log);
void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason);
private: private:
enum class VpnStatus enum class VpnStatus
{ {
@ -52,20 +56,34 @@ private:
QMenu *contextMenu; QMenu *contextMenu;
QAction *openAction; QAction *openAction;
QAction *connectAction; QAction *connectAction;
QMenu *gatewaySwitchMenu;
QAction *clearAction;
QAction *quitAction; QAction *quitAction;
GPGateway gateway; bool isQuickConnect { false };
bool isSwitchingGateway { false };
PortalConfigResponse portalConfig; PortalConfigResponse portalConfig;
QString portal() const; void initSystemTrayIcon();
void initVpnStatus(); void initVpnStatus();
void doConnect(); void populateGatewayMenu();
void updateConnectionStatus(const VpnStatus &status); void updateConnectionStatus(const VpnStatus &status);
void portalLogin(const QString& portal); void doConnect();
void gatewayLogin() const; void portalLogin();
void gatewayLogin();
QString portal() const;
bool connected() const;
QList<GPGateway> allGateways() const;
void setAllGateways(QList<GPGateway> gateways);
GPGateway currentGateway() const;
void setCurrentGateway(const GPGateway gateway);
void clearSettings();
void quit(); void quit();
}; };
#endif // GPCLIENT_H #endif // GPCLIENT_H

View File

@ -1,5 +1,9 @@
#include "gpgateway.h" #include "gpgateway.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
GPGateway::GPGateway() GPGateway::GPGateway()
{ {
} }
@ -36,3 +40,58 @@ int GPGateway::priorityOf(QString ruleName) const
} }
return 0; 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<GPGateway> &gateways)
{
QJsonArray arr;
for (auto g : gateways) {
arr.append(g.toJsonObject());
}
QJsonDocument jsonDoc{ arr };
return QString::fromUtf8(jsonDoc.toJson());
}
QList<GPGateway> GPGateway::fromJson(const QString &jsonString)
{
QList<GPGateway> 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;
}

View File

@ -3,6 +3,7 @@
#include <QString> #include <QString>
#include <QMap> #include <QMap>
#include <QJsonObject>
class GPGateway class GPGateway
{ {
@ -16,6 +17,12 @@ public:
void setAddress(const QString &address); void setAddress(const QString &address);
void setPriorityRules(const QMap<QString, int> &priorityRules); void setPriorityRules(const QMap<QString, int> &priorityRules);
int priorityOf(QString ruleName) const; int priorityOf(QString ruleName) const;
QJsonObject toJsonObject() const;
QString toString() const;
static QString serialize(QList<GPGateway> &gateways);
static QList<GPGateway> fromJson(const QString &jsonString);
static GPGateway fromJsonObject(const QJsonObject &jsonObj);
private: private:
QString _name; QString _name;

View File

@ -21,27 +21,11 @@ QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params)
return networkManager->post(request, params); return networkManager->post(request, params);
} }
SAMLLoginWindow* gpclient::helper::samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl) GPGateway gpclient::helper::filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName)
{ {
SAMLLoginWindow *loginWindow = new SAMLLoginWindow; GPGateway gateway = gateways.first();
if (samlMethod == "POST") { for (GPGateway g : gateways) {
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<GPGateway> *gateways, const QString ruleName)
{
GPGateway gateway = gateways->first();
for (GPGateway g : *gateways) {
if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) { if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) {
gateway = g; gateway = g;
} }
@ -76,7 +60,7 @@ QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml)
void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText) void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText)
{ {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle("GlobalProtect"); msgBox.setWindowTitle("Notice");
msgBox.setText(message); msgBox.setText(message);
msgBox.setFixedWidth(500); msgBox.setFixedWidth(500);
msgBox.setStyleSheet("QLabel{min-width: 250px}"); 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); _settings->setValue(key, value);
} }
void gpclient::helper::settings::clear()
{
_settings->clear();
}

View File

@ -20,9 +20,7 @@ namespace gpclient {
QNetworkReply* createRequest(QString url, QByteArray params = nullptr); QNetworkReply* createRequest(QString url, QByteArray params = nullptr);
SAMLLoginWindow *samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl); GPGateway filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName);
GPGateway filterPreferredGateway(QList<GPGateway> *gateways, const QString ruleName);
QUrlQuery parseGatewayResponse(const QByteArray& xml); QUrlQuery parseGatewayResponse(const QByteArray& xml);
@ -36,6 +34,7 @@ namespace gpclient {
QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); QVariant get(const QString &key, const QVariant &defaultValue = QVariant());
void save(const QString &key, const QVariant &value); void save(const QString &key, const QVariant &value);
void clear();
} }
} }
} }

View File

@ -113,24 +113,27 @@ void PortalAuthenticator::samlAuth()
{ {
PLOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod(); PLOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod();
SAMLLoginWindow *loginWindow = samlLogin(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); SAMLLoginWindow *loginWindow = new SAMLLoginWindow;
if (!loginWindow) {
openMessageBox("SAML Login failed for portal");
return;
}
connect(loginWindow, &SAMLLoginWindow::success, this, &PortalAuthenticator::onSAMLLoginSuccess); connect(loginWindow, &SAMLLoginWindow::success, this, &PortalAuthenticator::onSAMLLoginSuccess);
connect(loginWindow, &SAMLLoginWindow::fail, this, &PortalAuthenticator::onSAMLLoginFail);
connect(loginWindow, &SAMLLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected); connect(loginWindow, &SAMLLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected);
loginWindow->login(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl);
} }
void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> &samlResult) void PortalAuthenticator::onSAMLLoginSuccess(const QMap<QString, QString> samlResult)
{ {
PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie"); PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie");
fetchConfig(samlResult.value("username"), "", 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) void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie)
{ {
LoginParams params; LoginParams params;
@ -146,7 +149,6 @@ void PortalAuthenticator::fetchConfig(QString username, QString password, QStrin
PLOGI << "Fetching the portal config from " << configUrl << " for user: " << username; PLOGI << "Fetching the portal config from " << configUrl << " for user: " << username;
QNetworkReply *reply = createRequest(configUrl, params.toUtf8()); QNetworkReply *reply = createRequest(configUrl, params.toUtf8());
connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished);
} }
@ -185,7 +187,7 @@ void PortalAuthenticator::onFetchConfigFinished()
normalLoginWindow->close(); 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) void PortalAuthenticator::emitFail(const QString& msg)

View File

@ -18,7 +18,7 @@ public:
void authenticate(); void authenticate();
signals: signals:
void success(const PortalConfigResponse&, const GPGateway&); void success(const PortalConfigResponse, const GPGateway, QList<GPGateway> allGateways);
void fail(const QString& msg); void fail(const QString& msg);
void preloginFailed(const QString& msg); void preloginFailed(const QString& msg);
@ -27,7 +27,8 @@ private slots:
void onPerformNormalLogin(const QString &username, const QString &password); void onPerformNormalLogin(const QString &username, const QString &password);
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 onFetchConfigFinished(); void onFetchConfigFinished();
private: private:

View File

@ -8,13 +8,11 @@ QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserau
QString PortalConfigResponse::xmlGateways = "gateways"; QString PortalConfigResponse::xmlGateways = "gateways";
PortalConfigResponse::PortalConfigResponse() PortalConfigResponse::PortalConfigResponse()
: _gateways(new QList<GPGateway>)
{ {
} }
PortalConfigResponse::~PortalConfigResponse() PortalConfigResponse::~PortalConfigResponse()
{ {
delete _gateways;
} }
PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml) PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml)
@ -33,7 +31,7 @@ PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml)
} else if (name == xmlPrelogonUserAuthCookie) { } else if (name == xmlPrelogonUserAuthCookie) {
response.setPrelogonUserAuthCookie(xmlReader.readElementText()); response.setPrelogonUserAuthCookie(xmlReader.readElementText());
} else if (name == xmlGateways) { } else if (name == xmlGateways) {
parseGateways(xmlReader, response.allGateways()); response.setAllGateways(parseGateways(xmlReader));
} }
} }
@ -55,20 +53,23 @@ QString PortalConfigResponse::password() const
return _password; return _password;
} }
void PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader, QList<GPGateway> *gateways) QList<GPGateway> PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader)
{ {
QList<GPGateway> gateways;
while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) { while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) {
xmlReader.readNext(); xmlReader.readNext();
// Parse the gateways -> external -> list -> entry // Parse the gateways -> external -> list -> entry
if (xmlReader.name() == "entry" && xmlReader.isStartElement()) { if (xmlReader.name() == "entry" && xmlReader.isStartElement()) {
GPGateway gateway; GPGateway g;
QString address = xmlReader.attributes().value("name").toString(); QString address = xmlReader.attributes().value("name").toString();
gateway.setAddress(address); g.setAddress(address);
gateway.setPriorityRules(parsePriorityRules(xmlReader)); g.setPriorityRules(parsePriorityRules(xmlReader));
gateway.setName(parseGatewayName(xmlReader)); g.setName(parseGatewayName(xmlReader));
gateways->append(gateway); gateways.append(g);
} }
} }
return gateways;
} }
QMap<QString, int> PortalConfigResponse::parsePriorityRules(QXmlStreamReader &xmlReader) QMap<QString, int> PortalConfigResponse::parsePriorityRules(QXmlStreamReader &xmlReader)
@ -112,11 +113,16 @@ QString PortalConfigResponse::prelogonUserAuthCookie() const
return _prelogonAuthCookie; return _prelogonAuthCookie;
} }
QList<GPGateway>* PortalConfigResponse::allGateways() QList<GPGateway> PortalConfigResponse::allGateways()
{ {
return _gateways; return _gateways;
} }
void PortalConfigResponse::setAllGateways(QList<GPGateway> gateways)
{
_gateways = gateways;
}
void PortalConfigResponse::setRawResponse(const QByteArray &response) void PortalConfigResponse::setRawResponse(const QByteArray &response)
{ {
_rawResponse = response; _rawResponse = response;

View File

@ -20,7 +20,8 @@ public:
QString password() const; QString password() const;
QString userAuthCookie() const; QString userAuthCookie() const;
QString prelogonUserAuthCookie() const; QString prelogonUserAuthCookie() const;
QList<GPGateway>* allGateways(); QList<GPGateway> allGateways();
void setAllGateways(QList<GPGateway> gateways);
void setUsername(const QString& username); void setUsername(const QString& username);
void setPassword(const QString& password); void setPassword(const QString& password);
@ -36,13 +37,13 @@ private:
QString _userAuthCookie; QString _userAuthCookie;
QString _prelogonAuthCookie; QString _prelogonAuthCookie;
QList<GPGateway> *_gateways; QList<GPGateway> _gateways;
void setRawResponse(const QByteArray& response); void setRawResponse(const QByteArray& response);
void setUserAuthCookie(const QString& cookie); void setUserAuthCookie(const QString& cookie);
void setPrelogonUserAuthCookie(const QString& cookie); void setPrelogonUserAuthCookie(const QString& cookie);
static void parseGateways(QXmlStreamReader &xmlReader, QList<GPGateway> *gateways); static QList<GPGateway> parseGateways(QXmlStreamReader &xmlReader);
static QMap<QString, int> parsePriorityRules(QXmlStreamReader &xmlReader); static QMap<QString, int> parsePriorityRules(QXmlStreamReader &xmlReader);
static QString parseGatewayName(QXmlStreamReader &xmlReader); static QString parseGatewayName(QXmlStreamReader &xmlReader);
}; };

BIN
GPClient/radio_selected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

View File

@ -4,5 +4,7 @@
<file>connected.png</file> <file>connected.png</file>
<file>pending.png</file> <file>pending.png</file>
<file>not_connected.png</file> <file>not_connected.png</file>
<file>radio_unselected.png</file>
<file>radio_selected.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -3,16 +3,17 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <plog/Log.h> #include <plog/Log.h>
#include <QWebEngineProfile> #include <QWebEngineProfile>
#include <QWebEngineView>
SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
: QDialog(parent) : QDialog(parent)
, webView(new EnhancedWebView(this))
{ {
setWindowTitle("GlobalProtect SAML Login"); setWindowTitle("GlobalProtect SAML Login");
setModal(true); setModal(true);
resize(700, 550); resize(700, 550);
QVBoxLayout *verticalLayout = new QVBoxLayout(this); QVBoxLayout *verticalLayout = new QVBoxLayout(this);
webView = new EnhancedWebView(this);
webView->setUrl(QUrl("about:blank")); webView->setUrl(QUrl("about:blank"));
// webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); // webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
verticalLayout->addWidget(webView); verticalLayout->addWidget(webView);
@ -33,12 +34,15 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event)
reject(); reject();
} }
void SAMLLoginWindow::login(QString url, QString html) void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl)
{ {
if (html.isEmpty()) { if (samlMethod == "POST") {
webView->load(QUrl(url)); webView->setHtml(samlRequest, preloingUrl);
} else if (samlMethod == "REDIRECT") {
webView->load(samlRequest);
} else { } else {
webView->setHtml(html, url); PLOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod;
emit fail("Unknown saml-auth-method, got " + samlMethod);
} }
} }

View File

@ -15,10 +15,11 @@ public:
explicit SAMLLoginWindow(QWidget *parent = nullptr); explicit SAMLLoginWindow(QWidget *parent = nullptr);
~SAMLLoginWindow(); ~SAMLLoginWindow();
void login(QString url, QString html = ""); void login(const QString samlMethod, const QString samlRequest, const QString preloingUrl);
signals: signals:
void success(QMap<QString, QString> samlResult); void success(QMap<QString, QString> samlResult);
void fail(const QString msg);
private slots: private slots:
void onResponseReceived(QJsonObject params); void onResponseReceived(QJsonObject params);

View File

@ -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 <QtCore/QMetaObject>
#include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QVariant>
/*
* 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;
}

View File

@ -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 <QtCore/QObject>
#include <QtDBus/QtDBus>
QT_BEGIN_NAMESPACE
class QByteArray;
template<class T> class QList;
template<class Key, class Value> 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", ""
" <interface name=\"com.yuezk.qt.GPService\">\n"
" <signal name=\"connected\"/>\n"
" <signal name=\"disconnected\"/>\n"
" <signal name=\"logAvailable\">\n"
" <arg type=\"s\" name=\"log\"/>\n"
" </signal>\n"
" <method name=\"connect\">\n"
" <arg direction=\"in\" type=\"s\" name=\"server\"/>\n"
" <arg direction=\"in\" type=\"s\" name=\"username\"/>\n"
" <arg direction=\"in\" type=\"s\" name=\"passwd\"/>\n"
" </method>\n"
" <method name=\"disconnect\"/>\n"
" <method name=\"status\">\n"
" <arg direction=\"out\" type=\"i\"/>\n"
" </method>\n"
" </interface>\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

View File

@ -7,9 +7,10 @@ A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Q
## Features ## 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. - 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 ## Prerequisites