Compare commits

..

13 Commits

Author SHA1 Message Date
Kevin Yue
4fa08c7153 Fix locale for GPService 2020-05-24 23:33:49 +08:00
Kevin Yue
599ff3668f Add support to switch gateway 2020-05-24 22:41:53 +08:00
Kevin Yue
e22bb8e1b7 Improve code 2020-05-23 21:34:27 +08:00
Kevin Yue
7f5bf0ce52 Code refactor, support multiple gateways and non-SAML authentication (#9)
* Code refactor

* Update README.md
2020-05-23 15:51:10 +08:00
Kevin Yue
76a4977e92 Update README.md 2020-03-22 13:32:19 +08:00
Kevin Yue
246ef6d9ed Update README.md 2020-03-22 12:51:43 +08:00
Johannes
0ccb1371ab Bugfix for KDE build with Qt 5.11 from Debian 10 (#3)
Co-authored-by: Johannes Braun <jobraun@PC080>
2020-03-16 21:48:07 +08:00
Kevin Yue
81d4f9836f Merge pull request #2 from havocbane/master
Print QNetworkReply::NetworkError code to logs
2020-03-14 12:14:43 +08:00
Joseph Bane
cf32e44366 When prelogin fails, print QNetworkReply::NetworkError code to logs as well to aid debugging. 2020-03-13 12:29:44 -04:00
Kevin Yue
bdad3ffe4d Add support for okta saml login 2020-02-23 14:24:01 +08:00
Kevin Yue
cc59f031b0 Update README.md 2020-02-21 23:16:26 +08:00
Kevin Yue
d31598eac3 Update bin path 2020-02-21 22:31:51 +08:00
Kevin Yue
86dd501506 Change install folder 2020-02-21 22:22:24 +08:00
39 changed files with 1963 additions and 341 deletions

4
.gitignore vendored
View File

@@ -2,6 +2,10 @@
gpclient
gpservice
# Auto generated DBus files
*_adaptor.cpp
*_adaptor.h
# C++ objects and libs
*.slo
*.lo

4
.gitmodules vendored
View File

@@ -1,3 +1,7 @@
[submodule "singleapplication"]
path = singleapplication
url = https://github.com/itay-grudev/SingleApplication.git
[submodule "plog"]
path = plog
url = https://github.com/SergiusTheBest/plog.git

View File

@@ -15,6 +15,8 @@ DEFINES += QAPPLICATION_CLASS=QApplication
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
INCLUDEPATH += ../plog/include
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
@@ -23,7 +25,15 @@ SOURCES += \
cdpcommand.cpp \
cdpcommandmanager.cpp \
enhancedwebview.cpp \
gatewayauthenticator.cpp \
gpgateway.cpp \
gphelper.cpp \
loginparams.cpp \
main.cpp \
normalloginwindow.cpp \
portalauthenticator.cpp \
portalconfigresponse.cpp \
preloginresponse.cpp \
samlloginwindow.cpp \
gpclient.cpp
@@ -31,16 +41,25 @@ HEADERS += \
cdpcommand.h \
cdpcommandmanager.h \
enhancedwebview.h \
gatewayauthenticator.h \
gpgateway.h \
gphelper.h \
loginparams.h \
normalloginwindow.h \
portalauthenticator.h \
portalconfigresponse.h \
preloginresponse.h \
samlloginwindow.h \
gpclient.h
FORMS += \
gpclient.ui
gpclient.ui \
normalloginwindow.ui
DBUS_INTERFACES += ../GPService/gpservice.xml
# Default rules for deployment.
target.path = /usr/local/bin
target.path = /usr/bin
INSTALLS += target
DISTFILES += \

View File

@@ -1,5 +1,6 @@
#include "cdpcommandmanager.h"
#include <QVariantMap>
#include <plog/Log.h>
CDPCommandManager::CDPCommandManager(QObject *parent)
: QObject(parent)
@@ -27,7 +28,7 @@ void CDPCommandManager::initialize(QString endpoint)
reply, &QNetworkReply::finished,
[reply, this]() {
if (reply->error()) {
qDebug() << "CDP request error";
PLOGE << "CDP request error";
return;
}
@@ -76,10 +77,10 @@ void CDPCommandManager::onTextMessageReceived(QString message)
void CDPCommandManager::onSocketDisconnected()
{
qDebug() << "WebSocket disconnected";
PLOGI << "WebSocket disconnected";
}
void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error)
{
qDebug() << "WebSocket error" << error;
PLOGE << "WebSocket error" << error;
}

View File

@@ -4,7 +4,7 @@ Type=Application
Version=1.0.0
Name=GlobalProtect VPN
Comment=GlobalProtect VPN client, supports SAML auth mode
Exec=/usr/local/bin/gpclient
Exec=/usr/bin/gpclient
Icon=com.yuezk.qt.GPClient
Categories=Network;VPN;Utility;Qt;
Keywords=GlobalProtect;Openconnect;SAML;connection;VPN;

View File

@@ -0,0 +1,166 @@
#include "gatewayauthenticator.h"
#include "gphelper.h"
#include "loginparams.h"
#include "preloginresponse.h"
#include <QNetworkReply>
#include <plog/Log.h>
using namespace gpclient::helper;
GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, const PortalConfigResponse& portalConfig)
: QObject()
, preloginUrl("https://" + gateway + "/ssl-vpn/prelogin.esp")
, loginUrl("https://" + gateway + "/ssl-vpn/login.esp")
, portalConfig(portalConfig)
{
}
GatewayAuthenticator::~GatewayAuthenticator()
{
delete normalLoginWindow;
}
void GatewayAuthenticator::authenticate()
{
LoginParams params;
params.setUser(portalConfig.username());
params.setPassword(portalConfig.password());
params.setUserAuthCookie(portalConfig.userAuthCookie());
login(params);
}
void GatewayAuthenticator::login(const LoginParams &params)
{
PLOGI << "Trying to login the gateway at " << loginUrl << " with " << params.toUtf8();
QNetworkReply *reply = createRequest(loginUrl, params.toUtf8());
connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onLoginFinished);
}
void GatewayAuthenticator::onLoginFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) {
PLOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl).arg(reply->errorString());
if (normalLoginWindow) {
normalLoginWindow->setProcessing(false);
openMessageBox("Gateway login failed.", "Please check your credentials and try again.");
} else {
doAuth();
}
return;
}
if (normalLoginWindow) {
normalLoginWindow->close();
}
const QUrlQuery params = gpclient::helper::parseGatewayResponse(reply->readAll());
emit success(params.toString());
}
void GatewayAuthenticator::doAuth()
{
PLOGI << "Perform the gateway prelogin at " << preloginUrl;
QNetworkReply *reply = createRequest(preloginUrl);
connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onPreloginFinished);
}
void GatewayAuthenticator::onPreloginFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) {
PLOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl).arg(reply->errorString());
emit fail("Error occurred on the gateway prelogin interface.");
return;
}
PLOGI << "Gateway prelogin succeeded.";
PreloginResponse response = PreloginResponse::parse(reply->readAll());
if (response.hasSamlAuthFields()) {
samlAuth(response.samlMethod(), response.samlRequest(), reply->url().toString());
} else if (response.hasNormalAuthFields()) {
normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage());
} else {
PLOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl).arg(QString::fromUtf8(response.rawResponse()));
emit fail("Unknown response for gateway prelogin interface.");
}
delete reply;
}
void GatewayAuthenticator::normalAuth(QString labelUsername, QString labelPassword, QString authMessage)
{
PLOGI << QString("Trying to perform the normal login with %1 / %2 credentials").arg(labelUsername).arg(labelPassword);
normalLoginWindow = new NormalLoginWindow;
normalLoginWindow->setPortalAddress(gateway);
normalLoginWindow->setAuthMessage(authMessage);
normalLoginWindow->setUsernameLabel(labelUsername);
normalLoginWindow->setPasswordLabel(labelPassword);
// Do login
connect(normalLoginWindow, &NormalLoginWindow::performLogin, this, &GatewayAuthenticator::onPerformNormalLogin);
connect(normalLoginWindow, &NormalLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected);
connect(normalLoginWindow, &NormalLoginWindow::finished, this, &GatewayAuthenticator::onLoginWindowFinished);
normalLoginWindow->show();
}
void GatewayAuthenticator::onPerformNormalLogin(const QString &username, const QString &password)
{
normalLoginWindow->setProcessing(true);
LoginParams params;
params.setUser(username);
params.setPassword(password);
login(params);
}
void GatewayAuthenticator::onLoginWindowRejected()
{
emit fail();
}
void GatewayAuthenticator::onLoginWindowFinished()
{
delete normalLoginWindow;
normalLoginWindow = nullptr;
}
void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl)
{
PLOGI << "Trying to perform SAML login with saml-method " << samlMethod;
SAMLLoginWindow *loginWindow = new SAMLLoginWindow;
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::onSAMLLoginSuccess(const QMap<QString, QString> &samlResult)
{
PLOGI << "SAML login succeeded, got the prelogin cookie " << samlResult.value("preloginCookie");
LoginParams params;
params.setUser(samlResult.value("username"));
params.setPreloginCookie(samlResult.value("preloginCookie"));
login(params);
}
void GatewayAuthenticator::onSAMLLoginFail(const QString msg)
{
emit fail(msg);
}

View File

@@ -0,0 +1,46 @@
#ifndef GATEWAYAUTHENTICATOR_H
#define GATEWAYAUTHENTICATOR_H
#include "portalconfigresponse.h"
#include "normalloginwindow.h"
#include "loginparams.h"
#include <QObject>
class GatewayAuthenticator : public QObject
{
Q_OBJECT
public:
explicit GatewayAuthenticator(const QString& gateway, const PortalConfigResponse& portalConfig);
~GatewayAuthenticator();
void authenticate();
signals:
void success(const QString& authCookie);
void fail(const QString& msg = "");
private slots:
void onLoginFinished();
void onPreloginFinished();
void onPerformNormalLogin(const QString &username, const QString &password);
void onLoginWindowRejected();
void onLoginWindowFinished();
void onSAMLLoginSuccess(const QMap<QString, QString> &samlResult);
void onSAMLLoginFail(const QString msg);
private:
QString gateway;
QString preloginUrl;
QString loginUrl;
const PortalConfigResponse& portalConfig;
NormalLoginWindow *normalLoginWindow{ nullptr };
void login(const LoginParams& params);
void doAuth();
void normalAuth(QString labelUsername, QString labelPassword, QString authMessage);
void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = "");
};
#endif // GATEWAYAUTHENTICATOR_H

View File

@@ -1,227 +1,406 @@
#include "gpclient.h"
#include "gphelper.h"
#include "ui_gpclient.h"
#include "samlloginwindow.h"
#include "portalauthenticator.h"
#include "gatewayauthenticator.h"
#include <QDesktopWidget>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QImage>
#include <QStyle>
#include <QMessageBox>
#include <plog/Log.h>
#include <QIcon>
using namespace gpclient::helper;
GPClient::GPClient(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::GPClient)
{
ui->setupUi(this);
setWindowTitle("GlobalProtect");
setFixedSize(width(), height());
moveCenter();
gpclient::helper::moveCenter(this);
// Restore portal from the previous settings
settings = new QSettings("com.yuezk.qt", "GPClient");
ui->portalInput->setText(settings->value("portal", "").toString());
QObject::connect(this, &GPClient::connectFailed, [this]() {
updateConnectionStatus("not_connected");
});
// QNetworkAccessManager setup
networkManager = new QNetworkAccessManager(this);
ui->portalInput->setText(settings::get("portal", "").toString());
// DBus service setup
vpn = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this);
QObject::connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected);
QObject::connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected);
QObject::connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable);
connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected);
connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected);
connect(vpn, &com::yuezk::qt::GPService::logAvailable, this, &GPClient::onVPNLogAvailable);
// Initiallize the context menu of system tray.
initSystemTrayIcon();
initVpnStatus();
}
GPClient::~GPClient()
{
delete ui;
delete networkManager;
delete reply;
delete vpn;
delete settings;
}
void GPClient::on_connectButton_clicked()
{
QString btnText = ui->connectButton->text();
if (btnText == "Connect") {
QString portal = ui->portalInput->text();
settings->setValue("portal", portal);
ui->statusLabel->setText("Authenticating...");
updateConnectionStatus("pending");
doAuth(portal);
} else if (btnText == "Cancel") {
ui->statusLabel->setText("Canceling...");
updateConnectionStatus("pending");
if (reply->isRunning()) {
reply->abort();
}
vpn->disconnect();
} else {
ui->statusLabel->setText("Disconnecting...");
updateConnectionStatus("pending");
vpn->disconnect();
}
doConnect();
}
void GPClient::preloginResultFinished()
void GPClient::on_portalInput_returnPressed()
{
if (reply->error()) {
qWarning() << "Prelogin request error";
emit connectFailed();
return;
}
QByteArray bytes = reply->readAll();
const QString tagMethod = "saml-auth-method";
const QString tagRequest = "saml-request";
QString samlMethod;
QString samlRequest;
QXmlStreamReader xml(bytes);
while (!xml.atEnd()) {
xml.readNext();
if (xml.tokenType() == xml.StartElement) {
if (xml.name() == tagMethod) {
samlMethod = xml.readElementText();
} else if (xml.name() == tagRequest) {
samlRequest = QByteArray::fromBase64(QByteArray::fromStdString(xml.readElementText().toStdString()));
}
}
}
if (samlMethod == nullptr || samlRequest == nullptr) {
qWarning("This does not appear to be a SAML prelogin response (<saml-auth-method> or <saml-request> tags missing)");
emit connectFailed();
return;
}
if (samlMethod == "POST") {
// TODO
emit connectFailed();
QMessageBox msgBox;
msgBox.setText("TODO: SAML method is POST");
msgBox.exec();
} else if (samlMethod == "REDIRECT") {
samlLogin(samlRequest);
}
doConnect();
}
void GPClient::onLoginSuccess(QJsonObject loginResult)
void GPClient::on_portalInput_editingFinished()
{
QString fullpath = "/ssl-vpn/login.esp";
QString shortpath = "gateway";
QString user = loginResult.value("saml-username").toString();
QString cookieName;
QString cookieValue;
QString cookies[]{"prelogin-cookie", "portal-userauthcookie"};
for (int i = 0; i < cookies->length(); i++) {
cookieValue = loginResult.value(cookies[i]).toString();
if (cookieValue != nullptr) {
cookieName = cookies[i];
break;
}
}
QString host = QString("https://%1/%2:%3").arg(loginResult.value("server").toString(), shortpath, cookieName);
vpn->connect(host, user, cookieValue);
ui->statusLabel->setText("Connecting...");
updateConnectionStatus("pending");
populateGatewayMenu();
}
void GPClient::updateConnectionStatus(QString status)
void GPClient::initSystemTrayIcon()
{
if (status == "not_connected") {
ui->statusLabel->setText("Not Connected");
ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;");
ui->connectButton->setText("Connect");
ui->connectButton->setDisabled(false);
} else if (status == "pending") {
ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;");
ui->connectButton->setText("Cancel");
ui->connectButton->setDisabled(false);
} else if (status == "connected") {
ui->statusLabel->setText("Connected");
ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;");
ui->connectButton->setText("Disconnect");
ui->connectButton->setDisabled(false);
}
}
systemTrayIcon = new QSystemTrayIcon(this);
contextMenu = new QMenu("GlobalProtect", this);
void GPClient::onVPNConnected()
{
updateConnectionStatus("connected");
}
gatewaySwitchMenu = new QMenu("Switch Gateway", this);
gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup"));
populateGatewayMenu();
void GPClient::onVPNDisconnected()
{
updateConnectionStatus("not_connected");
}
systemTrayIcon->setIcon(QIcon(":/images/not_connected.png"));
systemTrayIcon->setToolTip("GlobalProtect");
systemTrayIcon->setContextMenu(contextMenu);
void GPClient::onVPNLogAvailable(QString log)
{
qInfo() << log;
connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated);
connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged);
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);
systemTrayIcon->show();
}
void GPClient::initVpnStatus() {
int status = vpn->status();
if (status == 1) {
ui->statusLabel->setText("Connecting...");
updateConnectionStatus("pending");
updateConnectionStatus(VpnStatus::pending);
} else if (status == 2) {
updateConnectionStatus("connected");
updateConnectionStatus(VpnStatus::connected);
} else if (status == 3) {
ui->statusLabel->setText("Disconnecting...");
updateConnectionStatus("pending");
updateConnectionStatus(VpnStatus::pending);
} else {
updateConnectionStatus(VpnStatus::disconnected);
}
}
void GPClient::moveCenter()
void GPClient::populateGatewayMenu()
{
QDesktopWidget *desktop = QApplication::desktop();
const QList<GPGateway> gateways = allGateways();
gatewaySwitchMenu->clear();
int screenWidth, width;
int screenHeight, height;
int x, y;
QSize windowSize;
if (gateways.isEmpty()) {
gatewaySwitchMenu->addAction("<None>")->setData(-1);
return;
}
screenWidth = desktop->width();
screenHeight = desktop->height();
windowSize = size();
width = windowSize.width();
height = windowSize.height();
x = (screenWidth - width) / 2;
y = (screenHeight - height) / 2;
y -= 50;
move(x, y);
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::doAuth(const QString portal)
void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status)
{
const QString preloginUrl = "https://" + portal + "/ssl-vpn/prelogin.esp";
reply = networkManager->post(QNetworkRequest(preloginUrl), (QByteArray) nullptr);
connect(reply, &QNetworkReply::finished, this, &GPClient::preloginResultFinished);
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::samlLogin(const QString loginUrl)
void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason)
{
SAMLLoginWindow *loginWindow = new SAMLLoginWindow(this);
QObject::connect(loginWindow, &SAMLLoginWindow::success, this, &GPClient::onLoginSuccess);
QObject::connect(loginWindow, &SAMLLoginWindow::rejected, this, &GPClient::connectFailed);
loginWindow->login(loginUrl);
loginWindow->exec();
delete loginWindow;
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;
}
if (btnText.endsWith("Connect")) {
settings::save("portal", 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);
vpn->disconnect();
}
}
// Login to the portal interface to get the portal config and preferred gateway
void GPClient::portalLogin()
{
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
connect(portalAuth, &PortalAuthenticator::preloginFailed, this, &GPClient::onPortalPreloginFail);
// 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, QList<GPGateway> allGateways)
{
this->portalConfig = portalConfig;
setAllGateways(allGateways);
setCurrentGateway(gateway);
gatewayLogin();
}
void GPClient::onPortalPreloginFail()
{
PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface...";
// Treat the portal input as the gateway address
GPGateway g;
g.setName(portal());
g.setAddress(portal());
QList<GPGateway> gateways;
gateways.append(g);
setAllGateways(gateways);
setCurrentGateway(g);
gatewayLogin();
}
void GPClient::onPortalFail(const QString &msg)
{
if (!msg.isEmpty()) {
openMessageBox("Portal authentication failed.", msg);
}
updateConnectionStatus(VpnStatus::disconnected);
}
// Login to the gateway
void GPClient::gatewayLogin()
{
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<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()
{
vpn->disconnect();
QApplication::quit();
}
void GPClient::onVPNConnected()
{
updateConnectionStatus(VpnStatus::connected);
}
void GPClient::onVPNDisconnected()
{
updateConnectionStatus(VpnStatus::disconnected);
if (isSwitchingGateway) {
gatewayLogin();
isSwitchingGateway = false;
}
}
void GPClient::onVPNLogAvailable(QString log)
{
PLOGI << log;
}

View File

@@ -2,9 +2,11 @@
#define GPCLIENT_H
#include "gpservice_interface.h"
#include "portalconfigresponse.h"
#include <QMainWindow>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSystemTrayIcon>
#include <QMenu>
QT_BEGIN_NAMESPACE
namespace Ui { class GPClient; }
@@ -18,30 +20,70 @@ public:
GPClient(QWidget *parent = nullptr);
~GPClient();
signals:
void connectFailed();
void activiate();
private slots:
void on_connectButton_clicked();
void preloginResultFinished();
void on_portalInput_returnPressed();
void on_portalInput_editingFinished();
void onLoginSuccess(QJsonObject loginResult);
void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason);
void onGatewayChanged(QAction *action);
void onPortalSuccess(const PortalConfigResponse portalConfig, const GPGateway gateway, QList<GPGateway> allGateways);
void onPortalPreloginFail();
void onPortalFail(const QString &msg);
void onGatewaySuccess(const QString &authCookie);
void onGatewayFail(const QString &msg);
void onVPNConnected();
void onVPNDisconnected();
void onVPNLogAvailable(QString log);
private:
Ui::GPClient *ui;
QNetworkAccessManager *networkManager;
QNetworkReply *reply;
com::yuezk::qt::GPService *vpn;
QSettings *settings;
enum class VpnStatus
{
disconnected,
pending,
connected
};
Ui::GPClient *ui;
com::yuezk::qt::GPService *vpn;
QSystemTrayIcon *systemTrayIcon;
QMenu *contextMenu;
QAction *openAction;
QAction *connectAction;
QMenu *gatewaySwitchMenu;
QAction *clearAction;
QAction *quitAction;
bool isQuickConnect { false };
bool isSwitchingGateway { false };
PortalConfigResponse portalConfig;
void initSystemTrayIcon();
void initVpnStatus();
void moveCenter();
void updateConnectionStatus(QString status);
void doAuth(const QString portal);
void samlLogin(const QString loginUrl);
void populateGatewayMenu();
void updateConnectionStatus(const VpnStatus &status);
void doConnect();
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();
};
#endif // GPCLIENT_H

View File

@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>GP VPN Client</string>
<string>GlobalProtect OpenConnect</string>
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
@@ -113,6 +113,12 @@
<property name="text">
<string>Connect</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
</layout>

97
GPClient/gpgateway.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "gpgateway.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
GPGateway::GPGateway()
{
}
QString GPGateway::name() const
{
return _name;
}
QString GPGateway::address() const
{
return _address;
}
void GPGateway::setName(const QString &name)
{
_name = name;
}
void GPGateway::setAddress(const QString &address)
{
_address = address;
}
void GPGateway::setPriorityRules(const QMap<QString, int> &priorityRules)
{
_priorityRules = priorityRules;
}
int GPGateway::priorityOf(QString ruleName) const
{
if (_priorityRules.contains(ruleName)) {
return _priorityRules.value(ruleName);
}
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;
}

33
GPClient/gpgateway.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef GPGATEWAY_H
#define GPGATEWAY_H
#include <QString>
#include <QMap>
#include <QJsonObject>
class GPGateway
{
public:
GPGateway();
QString name() const;
QString address() const;
void setName(const QString &name);
void setAddress(const QString &address);
void setPriorityRules(const QMap<QString, int> &priorityRules);
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:
QString _name;
QString _address;
QMap<QString, int> _priorityRules;
};
#endif // GPGATEWAY_H

108
GPClient/gphelper.cpp Normal file
View File

@@ -0,0 +1,108 @@
#include "gphelper.h"
#include <QNetworkRequest>
#include <QXmlStreamReader>
#include <QMessageBox>
#include <QDesktopWidget>
#include <QApplication>
#include <QWidget>
#include <plog/Log.h>
QNetworkAccessManager* gpclient::helper::networkManager = new QNetworkAccessManager;
QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params)
{
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, UA);
if (params == nullptr) {
return networkManager->post(request, QByteArray(nullptr));
}
return networkManager->post(request, params);
}
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)) {
gateway = g;
}
}
return gateway;
}
QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml)
{
QXmlStreamReader xmlReader{xml};
QList<QString> args;
while (!xmlReader.atEnd()) {
xmlReader.readNextStartElement();
if (xmlReader.name() == "argument") {
args.append(QUrl::toPercentEncoding(xmlReader.readElementText()));
}
}
QUrlQuery params{};
params.addQueryItem("authcookie", args.at(1));
params.addQueryItem("portal", args.at(3));
params.addQueryItem("user", args.at(4));
params.addQueryItem("domain", args.at(7));
params.addQueryItem("preferred-ip", args.at(15));
params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName()));
return params;
}
void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Notice");
msgBox.setText(message);
msgBox.setFixedWidth(500);
msgBox.setStyleSheet("QLabel{min-width: 250px}");
msgBox.setInformativeText(informativeText);
msgBox.exec();
}
void gpclient::helper::moveCenter(QWidget *widget)
{
QDesktopWidget *desktop = QApplication::desktop();
int screenWidth, width;
int screenHeight, height;
int x, y;
QSize windowSize;
screenWidth = desktop->width();
screenHeight = desktop->height();
windowSize = widget->size();
width = windowSize.width();
height = windowSize.height();
x = (screenWidth - width) / 2;
y = (screenHeight - height) / 2;
y -= 50;
widget->move(x, y);
}
QSettings *gpclient::helper::settings::_settings = new QSettings("com.yuezk.qt", "GPClient");
QVariant gpclient::helper::settings::get(const QString &key, const QVariant &defaultValue)
{
return _settings->value(key, defaultValue);
}
void gpclient::helper::settings::save(const QString &key, const QVariant &value)
{
_settings->setValue(key, value);
}
void gpclient::helper::settings::clear()
{
_settings->clear();
}

42
GPClient/gphelper.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef GPHELPER_H
#define GPHELPER_H
#include "samlloginwindow.h"
#include "gpgateway.h"
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QSettings>
const QString UA = "PAN GlobalProtect";
namespace gpclient {
namespace helper {
extern QNetworkAccessManager *networkManager;
QNetworkReply* createRequest(QString url, QByteArray params = nullptr);
GPGateway filterPreferredGateway(QList<GPGateway> gateways, const QString ruleName);
QUrlQuery parseGatewayResponse(const QByteArray& xml);
void openMessageBox(const QString& message, const QString& informativeText = "");
void moveCenter(QWidget *widget);
namespace settings {
extern QSettings *_settings;
QVariant get(const QString &key, const QVariant &defaultValue = QVariant());
void save(const QString &key, const QVariant &value);
void clear();
}
}
}
#endif // GPHELPER_H

70
GPClient/loginparams.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "loginparams.h"
#include <QUrlQuery>
LoginParams::LoginParams()
{
params.addQueryItem("prot", QUrl::toPercentEncoding("https:"));
params.addQueryItem("server", "");
params.addQueryItem("inputSrc", "");
params.addQueryItem("jnlpReady", "jnlpReady");
params.addQueryItem("user", "");
params.addQueryItem("passwd", "");
params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName()));
params.addQueryItem("ok", "Login");
params.addQueryItem("direct", "yes");
params.addQueryItem("clientVer", "4100");
params.addQueryItem("os-version", QUrl::toPercentEncoding(QSysInfo::prettyProductName()));
params.addQueryItem("clientos", "Linux");
params.addQueryItem("portal-userauthcookie", "");
params.addQueryItem("portal-prelogonuserauthcookie", "");
params.addQueryItem("prelogin-cookie", "");
params.addQueryItem("ipv6-support", "yes");
}
LoginParams::~LoginParams()
{
}
void LoginParams::setUser(const QString &user)
{
updateQueryItem("user", user);
}
void LoginParams::setServer(const QString &server)
{
updateQueryItem("server", server);
}
void LoginParams::setPassword(const QString &password)
{
updateQueryItem("passwd", password);
}
void LoginParams::setUserAuthCookie(const QString &cookie)
{
updateQueryItem("portal-userauthcookie", cookie);
}
void LoginParams::setPrelogonAuthCookie(const QString &cookie)
{
updateQueryItem("portal-prelogonuserauthcookie", cookie);
}
void LoginParams::setPreloginCookie(const QString &cookie)
{
updateQueryItem("prelogin-cookie", cookie);
}
QByteArray LoginParams::toUtf8() const
{
return params.toString().toUtf8();
}
void LoginParams::updateQueryItem(const QString &key, const QString &value)
{
if (params.hasQueryItem(key)) {
params.removeQueryItem(key);
}
params.addQueryItem(key, QUrl::toPercentEncoding(value));
}

27
GPClient/loginparams.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef LOGINPARAMS_H
#define LOGINPARAMS_H
#include <QUrlQuery>
class LoginParams
{
public:
LoginParams();
~LoginParams();
void setUser(const QString &user);
void setServer(const QString &server);
void setPassword(const QString &password);
void setUserAuthCookie(const QString &cookie);
void setPrelogonAuthCookie(const QString &cookie);
void setPreloginCookie(const QString &cookie);
QByteArray toUtf8() const;
private:
QUrlQuery params;
void updateQueryItem(const QString &key, const QString &value);
};
#endif // LOGINPARAMS_H

View File

@@ -2,17 +2,32 @@
#include "gpclient.h"
#include "enhancedwebview.h"
#include <QStandardPaths>
#include <plog/Log.h>
#include <plog/Appenders/ColorConsoleAppender.h>
int main(int argc, char *argv[])
{
const QDir path = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/GlobalProtect-openconnect";
const QString logFile = path.path() + "/gpclient.log";
if (!path.exists()) {
path.mkpath(".");
}
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
plog::init(plog::debug, logFile.toUtf8()).addAppender(&consoleAppender);
QString port = QString::fromLocal8Bit(qgetenv(ENV_CDP_PORT));
if (port == "") {
qputenv(ENV_CDP_PORT, "12315");
}
SingleApplication app(argc, argv);
GPClient w;
w.show();
QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::raise);
QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activiate);
return app.exec();
}

View File

@@ -0,0 +1,64 @@
#include "normalloginwindow.h"
#include "ui_normalloginwindow.h"
#include <QCloseEvent>
NormalLoginWindow::NormalLoginWindow(QWidget *parent) :
QDialog(parent),
ui(new Ui::NormalLoginWindow)
{
ui->setupUi(this);
setWindowTitle("GlobalProtect Login");
setFixedSize(width(), height());
setModal(true);
}
NormalLoginWindow::~NormalLoginWindow()
{
delete ui;
}
void NormalLoginWindow::setAuthMessage(QString message)
{
ui->authMessage->setText(message);
}
void NormalLoginWindow::setUsernameLabel(QString label)
{
ui->username->setPlaceholderText(label);
}
void NormalLoginWindow::setPasswordLabel(QString label)
{
ui->password->setPlaceholderText(label);
}
void NormalLoginWindow::setPortalAddress(QString portal)
{
ui->portalAddress->setText(portal);
}
void NormalLoginWindow::setProcessing(bool isProcessing)
{
ui->username->setReadOnly(isProcessing);
ui->password->setReadOnly(isProcessing);
ui->loginButton->setDisabled(isProcessing);
}
void NormalLoginWindow::on_loginButton_clicked()
{
const QString username = ui->username->text().trimmed();
const QString password = ui->password->text().trimmed();
if (username.isEmpty() || password.isEmpty()) {
return;
}
emit performLogin(username, password);
}
void NormalLoginWindow::closeEvent(QCloseEvent *event)
{
event->accept();
reject();
}

View File

@@ -0,0 +1,37 @@
#ifndef PORTALAUTHWINDOW_H
#define PORTALAUTHWINDOW_H
#include <QDialog>
namespace Ui {
class NormalLoginWindow;
}
class NormalLoginWindow : public QDialog
{
Q_OBJECT
public:
explicit NormalLoginWindow(QWidget *parent = nullptr);
~NormalLoginWindow();
void setAuthMessage(QString);
void setUsernameLabel(QString);
void setPasswordLabel(QString);
void setPortalAddress(QString);
void setProcessing(bool isProcessing);
private slots:
void on_loginButton_clicked();
signals:
void performLogin(QString username, QString password);
private:
Ui::NormalLoginWindow *ui;
void closeEvent(QCloseEvent *event);
};
#endif // PORTALAUTHWINDOW_H

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NormalLoginWindow</class>
<widget class="QDialog" name="NormalLoginWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>255</width>
<height>269</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="cursor">
<cursorShape>ArrowCursor</cursorShape>
</property>
<property name="windowTitle">
<string>Login</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string>Login</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="authMessage">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>2</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Please enter the login credentials</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="portalLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Portal:</string>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="portalAddress">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>vpn.example.com</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLineEdit" name="username">
<property name="placeholderText">
<string>Username</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="password">
<property name="text">
<string/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Password</string>
</property>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loginButton">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,196 @@
#include "portalauthenticator.h"
#include "gphelper.h"
#include "normalloginwindow.h"
#include "samlloginwindow.h"
#include "loginparams.h"
#include "preloginresponse.h"
#include "portalconfigresponse.h"
#include "gpgateway.h"
#include <plog/Log.h>
#include <QNetworkReply>
using namespace gpclient::helper;
PortalAuthenticator::PortalAuthenticator(const QString& portal) : QObject()
, portal(portal)
, preloginUrl("https://" + portal + "/global-protect/prelogin.esp")
, configUrl("https://" + portal + "/global-protect/getconfig.esp")
{
}
PortalAuthenticator::~PortalAuthenticator()
{
delete normalLoginWindow;
}
void PortalAuthenticator::authenticate()
{
PLOGI << "Preform portal prelogin at " << preloginUrl;
QNetworkReply *reply = createRequest(preloginUrl);
connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished);
}
void PortalAuthenticator::onPreloginFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) {
PLOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl).arg(reply->errorString());
emit preloginFailed("Error occurred on the portal prelogin interface.");
delete reply;
return;
}
PLOGI << "Portal prelogin succeeded.";
preloginResponse = PreloginResponse::parse(reply->readAll());
if (preloginResponse.hasSamlAuthFields()) {
// Do SAML authentication
samlAuth();
} else if (preloginResponse.hasNormalAuthFields()) {
// Do normal username/password authentication
tryAutoLogin();
} else {
PLOGE << QString("Unknown prelogin response for %1 got %2").arg(preloginUrl).arg(QString::fromUtf8(preloginResponse.rawResponse()));
emitFail("Unknown response for portal prelogin interface.");
}
delete reply;
}
void PortalAuthenticator::tryAutoLogin()
{
const QString username = settings::get("username").toString();
const QString password = settings::get("password").toString();
if (!username.isEmpty() && !password.isEmpty()) {
PLOGI << "Trying auto login using the saved credentials";
isAutoLogin = true;
fetchConfig(settings::get("username").toString(), settings::get("password").toString());
} else {
normalAuth();
}
}
void PortalAuthenticator::normalAuth()
{
PLOGI << "Trying to launch the normal login window...";
normalLoginWindow = new NormalLoginWindow;
normalLoginWindow->setPortalAddress(portal);
normalLoginWindow->setAuthMessage(preloginResponse.authMessage());
normalLoginWindow->setUsernameLabel(preloginResponse.labelUsername());
normalLoginWindow->setPasswordLabel(preloginResponse.labelPassword());
// Do login
connect(normalLoginWindow, &NormalLoginWindow::performLogin, this, &PortalAuthenticator::onPerformNormalLogin);
connect(normalLoginWindow, &NormalLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected);
connect(normalLoginWindow, &NormalLoginWindow::finished, this, &PortalAuthenticator::onLoginWindowFinished);
normalLoginWindow->show();
}
void PortalAuthenticator::onPerformNormalLogin(const QString &username, const QString &password)
{
normalLoginWindow->setProcessing(true);
fetchConfig(username, password);
}
void PortalAuthenticator::onLoginWindowRejected()
{
emitFail();
}
void PortalAuthenticator::onLoginWindowFinished()
{
delete normalLoginWindow;
normalLoginWindow = nullptr;
}
void PortalAuthenticator::samlAuth()
{
PLOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod();
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<QString, QString> 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;
params.setServer(portal);
params.setUser(username);
params.setPassword(password);
params.setPreloginCookie(preloginCookie);
// Save the username and password for future use.
this->username = username;
this->password = password;
PLOGI << "Fetching the portal config from " << configUrl << " for user: " << username;
QNetworkReply *reply = createRequest(configUrl, params.toUtf8());
connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished);
}
void PortalAuthenticator::onFetchConfigFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) {
PLOGE << QString("Failed to fetch the portal config from %1, %2").arg(configUrl).arg(reply->errorString());
// Login failed, enable the fields of the normal login window
if (normalLoginWindow) {
normalLoginWindow->setProcessing(false);
openMessageBox("Portal login failed.", "Please check your credentials and try again.");
} else if (isAutoLogin) {
isAutoLogin = false;
normalAuth();
} else {
emitFail("Failed to fetch the portal config.");
}
return;
}
PLOGI << "Fetch the portal config succeeded.";
PortalConfigResponse response = PortalConfigResponse::parse(reply->readAll());
// Add the username & password to the response object
response.setUsername(username);
response.setPassword(password);
// Close the login window
if (normalLoginWindow) {
// Save the credentials for reuse
settings::save("username", username);
settings::save("password", password);
normalLoginWindow->close();
}
emit success(response, filterPreferredGateway(response.allGateways(), preloginResponse.region()), response.allGateways());
}
void PortalAuthenticator::emitFail(const QString& msg)
{
emit fail(msg);
}

View File

@@ -0,0 +1,54 @@
#ifndef PORTALAUTHENTICATOR_H
#define PORTALAUTHENTICATOR_H
#include "portalconfigresponse.h"
#include "normalloginwindow.h"
#include "samlloginwindow.h"
#include "preloginresponse.h"
#include <QObject>
class PortalAuthenticator : public QObject
{
Q_OBJECT
public:
explicit PortalAuthenticator(const QString& portal);
~PortalAuthenticator();
void authenticate();
signals:
void success(const PortalConfigResponse, const GPGateway, QList<GPGateway> allGateways);
void fail(const QString& msg);
void preloginFailed(const QString& msg);
private slots:
void onPreloginFinished();
void onPerformNormalLogin(const QString &username, const QString &password);
void onLoginWindowRejected();
void onLoginWindowFinished();
void onSAMLLoginSuccess(const QMap<QString, QString> samlResult);
void onSAMLLoginFail(const QString msg);
void onFetchConfigFinished();
private:
QString portal;
QString preloginUrl;
QString configUrl;
QString username;
QString password;
PreloginResponse preloginResponse;
bool isAutoLogin { false };
NormalLoginWindow *normalLoginWindow{ nullptr };
void tryAutoLogin();
void normalAuth();
void samlAuth();
void fetchConfig(QString username, QString password, QString preloginCookie = "");
void emitFail(const QString& msg = "");
};
#endif // PORTALAUTHENTICATOR_H

View File

@@ -0,0 +1,149 @@
#include "portalconfigresponse.h"
#include <QXmlStreamReader>
#include <plog/Log.h>
QString PortalConfigResponse::xmlUserAuthCookie = "portal-userauthcookie";
QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserauthcookie";
QString PortalConfigResponse::xmlGateways = "gateways";
PortalConfigResponse::PortalConfigResponse()
{
}
PortalConfigResponse::~PortalConfigResponse()
{
}
PortalConfigResponse PortalConfigResponse::parse(const QByteArray& xml)
{
QXmlStreamReader xmlReader(xml);
PortalConfigResponse response;
response.setRawResponse(xml);
while (!xmlReader.atEnd()) {
xmlReader.readNextStartElement();
QString name = xmlReader.name().toString();
if (name == xmlUserAuthCookie) {
response.setUserAuthCookie(xmlReader.readElementText());
} else if (name == xmlPrelogonUserAuthCookie) {
response.setPrelogonUserAuthCookie(xmlReader.readElementText());
} else if (name == xmlGateways) {
response.setAllGateways(parseGateways(xmlReader));
}
}
return response;
}
const QByteArray& PortalConfigResponse::rawResponse() const
{
return _rawResponse;
}
QString PortalConfigResponse::username() const
{
return _username;
}
QString PortalConfigResponse::password() const
{
return _password;
}
QList<GPGateway> PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader)
{
QList<GPGateway> gateways;
while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) {
xmlReader.readNext();
// Parse the gateways -> external -> list -> entry
if (xmlReader.name() == "entry" && xmlReader.isStartElement()) {
GPGateway g;
QString address = xmlReader.attributes().value("name").toString();
g.setAddress(address);
g.setPriorityRules(parsePriorityRules(xmlReader));
g.setName(parseGatewayName(xmlReader));
gateways.append(g);
}
}
return gateways;
}
QMap<QString, int> PortalConfigResponse::parsePriorityRules(QXmlStreamReader &xmlReader)
{
QMap<QString, int> priorityRules;
while (xmlReader.name() != "priority-rule" || !xmlReader.isEndElement()) {
xmlReader.readNext();
if (xmlReader.name() == "entry" && xmlReader.isStartElement()) {
QString ruleName = xmlReader.attributes().value("name").toString();
// Read the priority tag
xmlReader.readNextStartElement();
int ruleValue = xmlReader.readElementText().toUInt();
priorityRules.insert(ruleName, ruleValue);
}
}
return priorityRules;
}
QString PortalConfigResponse::parseGatewayName(QXmlStreamReader &xmlReader)
{
while (xmlReader.name() != "description" || !xmlReader.isEndElement()) {
xmlReader.readNext();
if (xmlReader.name() == "description" && xmlReader.tokenType() == xmlReader.StartElement) {
return xmlReader.readElementText();
}
}
PLOGE << "Error: <description> tag not found";
return "";
}
QString PortalConfigResponse::userAuthCookie() const
{
return _userAuthCookie;
}
QString PortalConfigResponse::prelogonUserAuthCookie() const
{
return _prelogonAuthCookie;
}
QList<GPGateway> PortalConfigResponse::allGateways()
{
return _gateways;
}
void PortalConfigResponse::setAllGateways(QList<GPGateway> gateways)
{
_gateways = gateways;
}
void PortalConfigResponse::setRawResponse(const QByteArray &response)
{
_rawResponse = response;
}
void PortalConfigResponse::setUsername(const QString& username)
{
_username = username;
}
void PortalConfigResponse::setPassword(const QString& password)
{
_password = password;
}
void PortalConfigResponse::setUserAuthCookie(const QString &cookie)
{
_userAuthCookie = cookie;
}
void PortalConfigResponse::setPrelogonUserAuthCookie(const QString &cookie)
{
_prelogonAuthCookie = cookie;
}

View File

@@ -0,0 +1,51 @@
#ifndef PORTALCONFIGRESPONSE_H
#define PORTALCONFIGRESPONSE_H
#include "gpgateway.h"
#include <QString>
#include <QList>
#include <QXmlStreamReader>
class PortalConfigResponse
{
public:
PortalConfigResponse();
~PortalConfigResponse();
static PortalConfigResponse parse(const QByteArray& xml);
const QByteArray& rawResponse() const;
QString username() const;
QString password() const;
QString userAuthCookie() const;
QString prelogonUserAuthCookie() const;
QList<GPGateway> allGateways();
void setAllGateways(QList<GPGateway> gateways);
void setUsername(const QString& username);
void setPassword(const QString& password);
private:
static QString xmlUserAuthCookie;
static QString xmlPrelogonUserAuthCookie;
static QString xmlGateways;
QByteArray _rawResponse;
QString _username;
QString _password;
QString _userAuthCookie;
QString _prelogonAuthCookie;
QList<GPGateway> _gateways;
void setRawResponse(const QByteArray& response);
void setUserAuthCookie(const QString& cookie);
void setPrelogonUserAuthCookie(const QString& cookie);
static QList<GPGateway> parseGateways(QXmlStreamReader &xmlReader);
static QMap<QString, int> parsePriorityRules(QXmlStreamReader &xmlReader);
static QString parseGatewayName(QXmlStreamReader &xmlReader);
};
#endif // PORTALCONFIGRESPONSE_H

View File

@@ -0,0 +1,97 @@
#include "preloginresponse.h"
#include <QXmlStreamReader>
#include <QMap>
QString PreloginResponse::xmlAuthMessage = "authentication-message";
QString PreloginResponse::xmlLabelUsername = "username-label";
QString PreloginResponse::xmlLabelPassword = "password-label";
QString PreloginResponse::xmlSamlMethod = "saml-auth-method";
QString PreloginResponse::xmlSamlRequest = "saml-request";
QString PreloginResponse::xmlRegion = "region";
PreloginResponse::PreloginResponse()
{
add(xmlAuthMessage, "");
add(xmlLabelUsername, "");
add(xmlLabelPassword, "");
add(xmlSamlMethod, "");
add(xmlSamlRequest, "");
add(xmlRegion, "");
}
PreloginResponse PreloginResponse::parse(const QByteArray& xml)
{
QXmlStreamReader xmlReader(xml);
PreloginResponse response;
response.setRawResponse(xml);
while (!xmlReader.atEnd()) {
xmlReader.readNextStartElement();
QString name = xmlReader.name().toString();
if (response.has(name)) {
response.add(name, xmlReader.readElementText());
}
}
return response;
}
const QByteArray& PreloginResponse::rawResponse() const
{
return _rawResponse;
}
QString PreloginResponse::authMessage() const
{
return resultMap.value(xmlAuthMessage);
}
QString PreloginResponse::labelUsername() const
{
return resultMap.value(xmlLabelUsername);
}
QString PreloginResponse::labelPassword() const
{
return resultMap.value(xmlLabelPassword);
}
QString PreloginResponse::samlMethod() const
{
return resultMap.value(xmlSamlMethod);
}
QString PreloginResponse::samlRequest() const
{
return QByteArray::fromBase64(resultMap.value(xmlSamlRequest).toUtf8());
}
QString PreloginResponse::region() const
{
return resultMap.value(xmlRegion);
}
bool PreloginResponse::hasSamlAuthFields() const
{
return !samlMethod().isEmpty() && !samlRequest().isEmpty();
}
bool PreloginResponse::hasNormalAuthFields() const
{
return !labelUsername().isEmpty() && !labelPassword().isEmpty();
}
void PreloginResponse::setRawResponse(const QByteArray &response)
{
_rawResponse = response;
}
bool PreloginResponse::has(const QString &name) const
{
return resultMap.contains(name);
}
void PreloginResponse::add(const QString &name, const QString &value)
{
resultMap.insert(name, value);
}

View File

@@ -0,0 +1,41 @@
#ifndef PRELOGINRESPONSE_H
#define PRELOGINRESPONSE_H
#include <QString>
#include <QMap>
class PreloginResponse
{
public:
PreloginResponse();
static PreloginResponse parse(const QByteArray& xml);
const QByteArray& rawResponse() const;
QString authMessage() const;
QString labelUsername() const;
QString labelPassword() const;
QString samlMethod() const;
QString samlRequest() const;
QString region() const;
bool hasSamlAuthFields() const;
bool hasNormalAuthFields() const;
private:
static QString xmlAuthMessage;
static QString xmlLabelUsername;
static QString xmlLabelPassword;
static QString xmlSamlMethod;
static QString xmlSamlRequest;
static QString xmlRegion;
QMap<QString, QString> resultMap;
QByteArray _rawResponse;
void setRawResponse(const QByteArray &response);
void add(const QString &name, const QString &value);
bool has(const QString &name) const;
};
#endif // PRELOGINRESPONSE_H

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>pending.png</file>
<file>not_connected.png</file>
<file>radio_unselected.png</file>
<file>radio_selected.png</file>
</qresource>
</RCC>

View File

@@ -1,19 +1,26 @@
#include "samlloginwindow.h"
#include <QVBoxLayout>
#include <plog/Log.h>
#include <QWebEngineProfile>
#include <QWebEngineView>
SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
: QDialog(parent)
, webView(new EnhancedWebView(this))
{
setWindowTitle("SAML Login");
resize(610, 406);
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);
webView->initialize();
QObject::connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived);
connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived);
connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished);
}
SAMLLoginWindow::~SAMLLoginWindow()
@@ -27,9 +34,16 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event)
reject();
}
void SAMLLoginWindow::login(QString url)
void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloingUrl)
{
webView->load(QUrl(url));
if (samlMethod == "POST") {
webView->setHtml(samlRequest, preloingUrl);
} else if (samlMethod == "REDIRECT") {
webView->load(samlRequest);
} else {
PLOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod;
emit fail("Unknown saml-auth-method, got " + samlMethod);
}
}
void SAMLLoginWindow::onResponseReceived(QJsonObject params)
@@ -43,17 +57,24 @@ void SAMLLoginWindow::onResponseReceived(QJsonObject params)
QJsonObject response = params.value("response").toObject();
QJsonObject headers = response.value("headers").toObject();
foreach (const QString& key, headers.keys()) {
if (key.startsWith("saml-") || key == "prelogin-cookie" || key == "portal-userauthcookie") {
samlResult.insert(key, headers.value(key));
}
}
const QString username = headers.value("saml-username").toString();
const QString preloginCookie = headers.value("prelogin-cookie").toString();
// Check the SAML result
if (samlResult.contains("saml-username")
&& (samlResult.contains("prelogin-cookie") || samlResult.contains("portal-userauthcookie"))) {
samlResult.insert("server", QUrl(response.value("url").toString()).authority());
emit success(samlResult);
accept();
if (!username.isEmpty() && !preloginCookie.isEmpty()) {
samlResult.insert("username", username);
samlResult.insert("preloginCookie", preloginCookie);
}
}
void SAMLLoginWindow::onLoadFinished()
{
LOGI << "Load finished " << this->webView->page()->url().toString();
// Check the SAML result
if (!samlResult.value("username").isEmpty() && !samlResult.value("preloginCookie").isEmpty()) {
emit success(samlResult);
accept();
} else {
this->show();
}
}

View File

@@ -4,7 +4,7 @@
#include "enhancedwebview.h"
#include <QDialog>
#include <QJsonObject>
#include <QMap>
#include <QCloseEvent>
class SAMLLoginWindow : public QDialog
@@ -15,17 +15,19 @@ public:
explicit SAMLLoginWindow(QWidget *parent = nullptr);
~SAMLLoginWindow();
void login(QString url);
void login(const QString samlMethod, const QString samlRequest, const QString preloingUrl);
signals:
void success(QJsonObject samlResult);
void success(QMap<QString, QString> samlResult);
void fail(const QString msg);
private slots:
void onResponseReceived(QJsonObject params);
void onLoadFinished();
private:
EnhancedWebView *webView;
QJsonObject samlResult;
QMap<QString, QString> samlResult;
void closeEvent(QCloseEvent *event);
};

View File

@@ -32,7 +32,7 @@ SOURCES += \
DBUS_ADAPTORS += gpservice.xml
# Default rules for deployment.
target.path = /usr/local/bin
target.path = /usr/bin
INSTALLS += target
DISTFILES += \

View File

@@ -1,5 +1,5 @@
[D-BUS Service]
Name=com.yuezk.qt.GPService
Exec=/usr/local/bin/gpservice
Exec=/usr/bin/gpservice
User=root
SystemdService=gpservice.service

View File

@@ -66,13 +66,10 @@ void GPService::connect(QString server, QString username, QString passwd)
args << QCoreApplication::arguments().mid(1)
<< "--protocol=gp"
<< "-u" << username
<< "--passwd-on-stdin"
<< "--timestamp"
<< "-C" << passwd
<< server;
openconnect->start(bin, args);
openconnect->write(passwd.toUtf8());
openconnect->closeWriteChannel();
}
void GPService::disconnect()
@@ -130,6 +127,5 @@ void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
void GPService::log(QString msg)
{
qInfo() << msg;
emit logAvailable(msg);
}

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

@@ -2,9 +2,10 @@
Description=GlobalProtect openconnect DBus service
[Service]
Environment="LANG=en_US.utf8"
Type=dbus
BusName=com.yuezk.qt.GPService
ExecStart=/usr/local/bin/gpservice
ExecStart=/usr/bin/gpservice
[Install]
WantedBy=multi-user.target

View File

@@ -5,6 +5,13 @@ A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Q
<img src="screenshot.png">
</p>
## Features
- 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
- Openconnect v8.x
@@ -12,17 +19,34 @@ A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Q
### Ubuntu
1. Install openconnect v8.x
Update openconnect to 8.x, for ubuntu 18.04 you might need to [build the latest openconnect from source code](https://gist.github.com/yuezk/ab9a4b87a9fa0182bdb2df41fab5f613).
For Ubuntu 18.04 you might need to [build the latest openconnect from source code](https://gist.github.com/yuezk/ab9a4b87a9fa0182bdb2df41fab5f613).
2. Install the Qt dependencies
```sh
sudo apt install qt5-default libqt5websockets5-dev qtwebengine5-dev
```
### OpenSUSE
Install the Qt dependencies
```sh
sudo zypper install libqt5-qtbase-devel libqt5-qtwebsockets-devel libqt5-qtwebengine-devel
```
## Install
### Install from AUR (Arch/Manjaro)
Install [globalprotect-openconnect](https://aur.archlinux.org/packages/globalprotect-openconnect/).
### Build from source code
```sh
git clone https://github.com/yuezk/GlobalProtect-openconnect.git
cd GlobalProtect-openconnect
git submodule init && git submodule update
git submodule update --init
# qmake or qmake-qt5
qmake CONFIG+=release
make
sudo make install

1
plog Submodule

Submodule plog added at fda4a26c26