mirror of
https://github.com/yuezk/GlobalProtect-openconnect.git
synced 2025-04-02 18:31:50 -04:00
Code refactor, support multiple gateways and non-SAML authentication (#9)
* Code refactor * Update README.md
This commit is contained in:
parent
76a4977e92
commit
7f5bf0ce52
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,3 +1,7 @@
|
|||||||
[submodule "singleapplication"]
|
[submodule "singleapplication"]
|
||||||
path = singleapplication
|
path = singleapplication
|
||||||
url = https://github.com/itay-grudev/SingleApplication.git
|
url = https://github.com/itay-grudev/SingleApplication.git
|
||||||
|
|
||||||
|
[submodule "plog"]
|
||||||
|
path = plog
|
||||||
|
url = https://github.com/SergiusTheBest/plog.git
|
||||||
|
@ -15,6 +15,8 @@ DEFINES += QAPPLICATION_CLASS=QApplication
|
|||||||
# deprecated API in order to know how to port your code away from it.
|
# deprecated API in order to know how to port your code away from it.
|
||||||
DEFINES += QT_DEPRECATED_WARNINGS
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
|
INCLUDEPATH += ../plog/include
|
||||||
|
|
||||||
# You can also make your code fail to compile if it uses deprecated APIs.
|
# You can also make your code fail to compile if it uses deprecated APIs.
|
||||||
# In order to do so, uncomment the following line.
|
# 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.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
@ -23,7 +25,15 @@ SOURCES += \
|
|||||||
cdpcommand.cpp \
|
cdpcommand.cpp \
|
||||||
cdpcommandmanager.cpp \
|
cdpcommandmanager.cpp \
|
||||||
enhancedwebview.cpp \
|
enhancedwebview.cpp \
|
||||||
|
gatewayauthenticator.cpp \
|
||||||
|
gpgateway.cpp \
|
||||||
|
gphelper.cpp \
|
||||||
|
loginparams.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
|
normalloginwindow.cpp \
|
||||||
|
portalauthenticator.cpp \
|
||||||
|
portalconfigresponse.cpp \
|
||||||
|
preloginresponse.cpp \
|
||||||
samlloginwindow.cpp \
|
samlloginwindow.cpp \
|
||||||
gpclient.cpp
|
gpclient.cpp
|
||||||
|
|
||||||
@ -31,11 +41,20 @@ HEADERS += \
|
|||||||
cdpcommand.h \
|
cdpcommand.h \
|
||||||
cdpcommandmanager.h \
|
cdpcommandmanager.h \
|
||||||
enhancedwebview.h \
|
enhancedwebview.h \
|
||||||
|
gatewayauthenticator.h \
|
||||||
|
gpgateway.h \
|
||||||
|
gphelper.h \
|
||||||
|
loginparams.h \
|
||||||
|
normalloginwindow.h \
|
||||||
|
portalauthenticator.h \
|
||||||
|
portalconfigresponse.h \
|
||||||
|
preloginresponse.h \
|
||||||
samlloginwindow.h \
|
samlloginwindow.h \
|
||||||
gpclient.h
|
gpclient.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
gpclient.ui
|
gpclient.ui \
|
||||||
|
normalloginwindow.ui
|
||||||
|
|
||||||
DBUS_INTERFACES += ../GPService/gpservice.xml
|
DBUS_INTERFACES += ../GPService/gpservice.xml
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "cdpcommandmanager.h"
|
#include "cdpcommandmanager.h"
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
#include <plog/Log.h>
|
||||||
|
|
||||||
CDPCommandManager::CDPCommandManager(QObject *parent)
|
CDPCommandManager::CDPCommandManager(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@ -27,7 +28,7 @@ void CDPCommandManager::initialize(QString endpoint)
|
|||||||
reply, &QNetworkReply::finished,
|
reply, &QNetworkReply::finished,
|
||||||
[reply, this]() {
|
[reply, this]() {
|
||||||
if (reply->error()) {
|
if (reply->error()) {
|
||||||
qDebug() << "CDP request error";
|
PLOGE << "CDP request error";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,10 +77,10 @@ void CDPCommandManager::onTextMessageReceived(QString message)
|
|||||||
|
|
||||||
void CDPCommandManager::onSocketDisconnected()
|
void CDPCommandManager::onSocketDisconnected()
|
||||||
{
|
{
|
||||||
qDebug() << "WebSocket disconnected";
|
PLOGI << "WebSocket disconnected";
|
||||||
}
|
}
|
||||||
|
|
||||||
void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error)
|
void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error)
|
||||||
{
|
{
|
||||||
qDebug() << "WebSocket error" << error;
|
PLOGE << "WebSocket error" << error;
|
||||||
}
|
}
|
||||||
|
160
GPClient/gatewayauthenticator.cpp
Normal file
160
GPClient/gatewayauthenticator.cpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#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 ¶ms)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
normalLoginWindow->exec();
|
||||||
|
delete normalLoginWindow;
|
||||||
|
normalLoginWindow = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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::samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl)
|
||||||
|
{
|
||||||
|
PLOGI << "Trying to perform SAML login with saml-method " << samlMethod;
|
||||||
|
|
||||||
|
SAMLLoginWindow *loginWindow = samlLogin(samlMethod, samlRequest, preloginUrl);
|
||||||
|
|
||||||
|
if (!loginWindow) {
|
||||||
|
openMessageBox("SAML Login failed for gateway");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(loginWindow, &SAMLLoginWindow::success, this, &GatewayAuthenticator::onSAMLLoginFinished);
|
||||||
|
connect(loginWindow, &SAMLLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected);
|
||||||
|
// loginWindow->exec();
|
||||||
|
// delete loginWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayAuthenticator::onSAMLLoginFinished(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);
|
||||||
|
}
|
44
GPClient/gatewayauthenticator.h
Normal file
44
GPClient/gatewayauthenticator.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#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 onSAMLLoginFinished(const QMap<QString, QString> &samlResult);
|
||||||
|
void onLoginWindowRejected();
|
||||||
|
|
||||||
|
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
|
@ -1,224 +1,262 @@
|
|||||||
#include "gpclient.h"
|
#include "gpclient.h"
|
||||||
|
#include "gphelper.h"
|
||||||
#include "ui_gpclient.h"
|
#include "ui_gpclient.h"
|
||||||
#include "samlloginwindow.h"
|
#include "portalauthenticator.h"
|
||||||
|
#include "gatewayauthenticator.h"
|
||||||
|
|
||||||
#include <QDesktopWidget>
|
#include <plog/Log.h>
|
||||||
#include <QGraphicsScene>
|
#include <QIcon>
|
||||||
#include <QGraphicsView>
|
|
||||||
#include <QGraphicsPixmapItem>
|
using namespace gpclient::helper;
|
||||||
#include <QImage>
|
|
||||||
#include <QStyle>
|
|
||||||
#include <QMessageBox>
|
|
||||||
|
|
||||||
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");
|
||||||
setFixedSize(width(), height());
|
setFixedSize(width(), height());
|
||||||
moveCenter();
|
gpclient::helper::moveCenter(this);
|
||||||
|
|
||||||
// Restore portal from the previous settings
|
// Restore portal from the previous settings
|
||||||
settings = new QSettings("com.yuezk.qt", "GPClient");
|
ui->portalInput->setText(settings::get("portal", "").toString());
|
||||||
ui->portalInput->setText(settings->value("portal", "").toString());
|
|
||||||
|
|
||||||
QObject::connect(this, &GPClient::connectFailed, [this]() {
|
|
||||||
updateConnectionStatus("not_connected");
|
|
||||||
});
|
|
||||||
|
|
||||||
// QNetworkAccessManager setup
|
|
||||||
networkManager = new QNetworkAccessManager(this);
|
|
||||||
|
|
||||||
// DBus service setup
|
// DBus service setup
|
||||||
vpn = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this);
|
vpn = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this);
|
||||||
QObject::connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected);
|
connect(vpn, &com::yuezk::qt::GPService::connected, this, &GPClient::onVPNConnected);
|
||||||
QObject::connect(vpn, &com::yuezk::qt::GPService::disconnected, this, &GPClient::onVPNDisconnected);
|
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::logAvailable, this, &GPClient::onVPNLogAvailable);
|
||||||
|
|
||||||
|
connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated);
|
||||||
|
|
||||||
|
// Initiallize the context menu of system tray.
|
||||||
|
openAction = contextMenu->addAction(QIcon::fromTheme("system-run"), "Open", this, &GPClient::activiate);
|
||||||
|
connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect);
|
||||||
|
contextMenu->addSeparator();
|
||||||
|
quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit);
|
||||||
|
systemTrayIcon->setContextMenu(contextMenu);
|
||||||
|
systemTrayIcon->setToolTip("GlobalProtect");
|
||||||
|
|
||||||
initVpnStatus();
|
initVpnStatus();
|
||||||
|
systemTrayIcon->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
GPClient::~GPClient()
|
GPClient::~GPClient()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
delete networkManager;
|
|
||||||
delete reply;
|
|
||||||
delete vpn;
|
delete vpn;
|
||||||
delete settings;
|
delete systemTrayIcon;
|
||||||
|
delete openAction;
|
||||||
|
delete connectAction;
|
||||||
|
delete quitAction;
|
||||||
|
delete contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::on_connectButton_clicked()
|
void GPClient::on_connectButton_clicked()
|
||||||
{
|
{
|
||||||
QString btnText = ui->connectButton->text();
|
doConnect();
|
||||||
|
|
||||||
if (btnText.endsWith("Connect")) {
|
|
||||||
QString portal = ui->portalInput->text();
|
|
||||||
settings->setValue("portal", portal);
|
|
||||||
ui->statusLabel->setText("Authenticating...");
|
|
||||||
updateConnectionStatus("pending");
|
|
||||||
doAuth(portal);
|
|
||||||
} else if (btnText.endsWith("Cancel")) {
|
|
||||||
ui->statusLabel->setText("Canceling...");
|
|
||||||
updateConnectionStatus("pending");
|
|
||||||
|
|
||||||
if (reply->isRunning()) {
|
|
||||||
reply->abort();
|
|
||||||
}
|
|
||||||
vpn->disconnect();
|
|
||||||
} else {
|
|
||||||
ui->statusLabel->setText("Disconnecting...");
|
|
||||||
updateConnectionStatus("pending");
|
|
||||||
vpn->disconnect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::preloginResultFinished()
|
void GPClient::on_portalInput_returnPressed()
|
||||||
{
|
{
|
||||||
QNetworkReply::NetworkError err = reply->error();
|
doConnect();
|
||||||
if (err) {
|
|
||||||
qWarning() << "Prelogin request error: " << err;
|
|
||||||
emit connectFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray xmlBytes = reply->readAll();
|
|
||||||
const QString tagMethod = "saml-auth-method";
|
|
||||||
const QString tagRequest = "saml-request";
|
|
||||||
QString samlMethod;
|
|
||||||
QString samlRequest;
|
|
||||||
|
|
||||||
QXmlStreamReader xml(xmlBytes);
|
|
||||||
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") {
|
|
||||||
samlLogin(reply->url().toString(), samlRequest);
|
|
||||||
} else if (samlMethod == "REDIRECT") {
|
|
||||||
samlLogin(samlRequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::onLoginSuccess(QJsonObject loginResult)
|
void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status)
|
||||||
{
|
{
|
||||||
QString fullpath = "/ssl-vpn/login.esp";
|
switch (status) {
|
||||||
QString shortpath = "gateway";
|
case VpnStatus::disconnected:
|
||||||
QString user = loginResult.value("saml-username").toString();
|
ui->statusLabel->setText("Not Connected");
|
||||||
QString cookieName;
|
ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;");
|
||||||
QString cookieValue;
|
ui->connectButton->setText("Connect");
|
||||||
QString cookies[]{"prelogin-cookie", "portal-userauthcookie"};
|
ui->connectButton->setDisabled(false);
|
||||||
|
ui->portalInput->setReadOnly(false);
|
||||||
|
|
||||||
for (int i = 0; i < cookies->length(); i++) {
|
systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" });
|
||||||
cookieValue = loginResult.value(cookies[i]).toString();
|
connectAction->setEnabled(true);
|
||||||
if (cookieValue != nullptr) {
|
connectAction->setText("Connect");
|
||||||
cookieName = cookies[i];
|
|
||||||
break;
|
break;
|
||||||
}
|
case VpnStatus::pending:
|
||||||
}
|
ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;");
|
||||||
|
ui->connectButton->setDisabled(true);
|
||||||
|
ui->portalInput->setReadOnly(true);
|
||||||
|
|
||||||
QString host = QString("https://%1/%2:%3").arg(loginResult.value("server").toString(), shortpath, cookieName);
|
systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" });
|
||||||
vpn->connect(host, user, cookieValue);
|
connectAction->setEnabled(false);
|
||||||
ui->statusLabel->setText("Connecting...");
|
break;
|
||||||
updateConnectionStatus("pending");
|
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);
|
||||||
|
|
||||||
void GPClient::updateConnectionStatus(QString status)
|
systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" });
|
||||||
{
|
connectAction->setEnabled(true);
|
||||||
if (status == "not_connected") {
|
connectAction->setText("Disconnect");
|
||||||
ui->statusLabel->setText("Not Connected");
|
break;
|
||||||
ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;");
|
default:
|
||||||
ui->connectButton->setText("Connect");
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::onVPNConnected()
|
void GPClient::onVPNConnected()
|
||||||
{
|
{
|
||||||
updateConnectionStatus("connected");
|
updateConnectionStatus(VpnStatus::connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::onVPNDisconnected()
|
void GPClient::onVPNDisconnected()
|
||||||
{
|
{
|
||||||
updateConnectionStatus("not_connected");
|
updateConnectionStatus(VpnStatus::disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::onVPNLogAvailable(QString log)
|
void GPClient::onVPNLogAvailable(QString log)
|
||||||
{
|
{
|
||||||
qInfo() << log;
|
PLOGI << log;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
||||||
|
{
|
||||||
|
switch (reason) {
|
||||||
|
case QSystemTrayIcon::Trigger:
|
||||||
|
case QSystemTrayIcon::DoubleClick:
|
||||||
|
this->activiate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::initVpnStatus() {
|
void GPClient::initVpnStatus() {
|
||||||
int status = vpn->status();
|
int status = vpn->status();
|
||||||
|
|
||||||
if (status == 1) {
|
if (status == 1) {
|
||||||
ui->statusLabel->setText("Connecting...");
|
ui->statusLabel->setText("Connecting...");
|
||||||
updateConnectionStatus("pending");
|
updateConnectionStatus(VpnStatus::pending);
|
||||||
} else if (status == 2) {
|
} else if (status == 2) {
|
||||||
updateConnectionStatus("connected");
|
updateConnectionStatus(VpnStatus::connected);
|
||||||
} else if (status == 3) {
|
} else if (status == 3) {
|
||||||
ui->statusLabel->setText("Disconnecting...");
|
ui->statusLabel->setText("Disconnecting...");
|
||||||
updateConnectionStatus("pending");
|
updateConnectionStatus(VpnStatus::pending);
|
||||||
|
} else {
|
||||||
|
updateConnectionStatus(VpnStatus::disconnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::moveCenter()
|
void GPClient::doConnect()
|
||||||
{
|
{
|
||||||
QDesktopWidget *desktop = QApplication::desktop();
|
const QString btnText = ui->connectButton->text();
|
||||||
|
const QString portal = this->portal();
|
||||||
|
|
||||||
int screenWidth, width;
|
if (portal.isEmpty()) {
|
||||||
int screenHeight, height;
|
activiate();
|
||||||
int x, y;
|
return;
|
||||||
QSize windowSize;
|
}
|
||||||
|
|
||||||
screenWidth = desktop->width();
|
if (btnText.endsWith("Connect")) {
|
||||||
screenHeight = desktop->height();
|
settings::save("portal", portal);
|
||||||
|
ui->statusLabel->setText("Authenticating...");
|
||||||
|
updateConnectionStatus(VpnStatus::pending);
|
||||||
|
|
||||||
windowSize = size();
|
// Perform the portal login
|
||||||
width = windowSize.width();
|
portalLogin(portal);
|
||||||
height = windowSize.height();
|
} else {
|
||||||
|
ui->statusLabel->setText("Disconnecting...");
|
||||||
|
updateConnectionStatus(VpnStatus::pending);
|
||||||
|
|
||||||
x = (screenWidth - width) / 2;
|
vpn->disconnect();
|
||||||
y = (screenHeight - height) / 2;
|
}
|
||||||
y -= 50;
|
|
||||||
move(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::doAuth(const QString portal)
|
// Login to the portal interface to get the portal config and preferred gateway
|
||||||
|
void GPClient::portalLogin(const QString& portal)
|
||||||
{
|
{
|
||||||
const QString preloginUrl = "https://" + portal + "/ssl-vpn/prelogin.esp";
|
PortalAuthenticator *portalAuth = new PortalAuthenticator(portal);
|
||||||
reply = networkManager->post(QNetworkRequest(preloginUrl), (QByteArray) nullptr);
|
|
||||||
connect(reply, &QNetworkReply::finished, this, &GPClient::preloginResultFinished);
|
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);
|
||||||
|
|
||||||
|
portalAuth->authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPClient::samlLogin(const QString loginUrl, const QString html)
|
void GPClient::onPortalSuccess(const PortalConfigResponse &portalConfig, const GPGateway &gateway)
|
||||||
{
|
{
|
||||||
SAMLLoginWindow *loginWindow = new SAMLLoginWindow(this);
|
this->portalConfig = portalConfig;
|
||||||
|
this->gateway = gateway;
|
||||||
|
|
||||||
QObject::connect(loginWindow, &SAMLLoginWindow::success, this, &GPClient::onLoginSuccess);
|
gatewayLogin();
|
||||||
QObject::connect(loginWindow, &SAMLLoginWindow::rejected, this, &GPClient::connectFailed);
|
}
|
||||||
|
|
||||||
loginWindow->login(loginUrl, html);
|
void GPClient::onPortalPreloginFail()
|
||||||
loginWindow->exec();
|
{
|
||||||
delete loginWindow;
|
PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface...";
|
||||||
|
|
||||||
|
// Set the gateway address to portal input
|
||||||
|
gateway.setAddress(portal());
|
||||||
|
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() const
|
||||||
|
{
|
||||||
|
GatewayAuthenticator *gatewayAuth = new GatewayAuthenticator(gateway.address(), portalConfig);
|
||||||
|
|
||||||
|
connect(gatewayAuth, &GatewayAuthenticator::success, this, &GPClient::onGatewaySuccess);
|
||||||
|
connect(gatewayAuth, &GatewayAuthenticator::fail, this, &GPClient::onGatewayFail);
|
||||||
|
|
||||||
|
gatewayAuth->authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPClient::quit()
|
||||||
|
{
|
||||||
|
vpn->disconnect();
|
||||||
|
QApplication::quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPClient::onGatewaySuccess(const QString &authCookie)
|
||||||
|
{
|
||||||
|
PLOGI << "Gateway login succeeded, got the cookie " << authCookie;
|
||||||
|
|
||||||
|
vpn->connect(gateway.address(), portalConfig.username(), authCookie);
|
||||||
|
ui->statusLabel->setText("Connecting...");
|
||||||
|
updateConnectionStatus(VpnStatus::pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPClient::onGatewayFail(const QString &msg)
|
||||||
|
{
|
||||||
|
if (!msg.isEmpty()) {
|
||||||
|
openMessageBox("Portal authentication failed.", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConnectionStatus(VpnStatus::disconnected);
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
#define GPCLIENT_H
|
#define GPCLIENT_H
|
||||||
|
|
||||||
#include "gpservice_interface.h"
|
#include "gpservice_interface.h"
|
||||||
|
#include "portalconfigresponse.h"
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QNetworkAccessManager>
|
#include <QSystemTrayIcon>
|
||||||
#include <QNetworkReply>
|
#include <QMenu>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
namespace Ui { class GPClient; }
|
namespace Ui { class GPClient; }
|
||||||
@ -17,31 +19,53 @@ class GPClient : public QMainWindow
|
|||||||
public:
|
public:
|
||||||
GPClient(QWidget *parent = nullptr);
|
GPClient(QWidget *parent = nullptr);
|
||||||
~GPClient();
|
~GPClient();
|
||||||
|
void activiate();
|
||||||
signals:
|
|
||||||
void connectFailed();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_connectButton_clicked();
|
void on_connectButton_clicked();
|
||||||
void preloginResultFinished();
|
void on_portalInput_returnPressed();
|
||||||
|
|
||||||
void onLoginSuccess(QJsonObject loginResult);
|
void onPortalSuccess(const PortalConfigResponse &portalConfig, const GPGateway &gateway);
|
||||||
|
void onPortalPreloginFail();
|
||||||
|
void onPortalFail(const QString &msg);
|
||||||
|
void onGatewaySuccess(const QString &authCookie);
|
||||||
|
void onGatewayFail(const QString &msg);
|
||||||
|
|
||||||
void onVPNConnected();
|
void onVPNConnected();
|
||||||
void onVPNDisconnected();
|
void onVPNDisconnected();
|
||||||
void onVPNLogAvailable(QString log);
|
void onVPNLogAvailable(QString log);
|
||||||
|
|
||||||
|
void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class VpnStatus
|
||||||
|
{
|
||||||
|
disconnected,
|
||||||
|
pending,
|
||||||
|
connected
|
||||||
|
};
|
||||||
|
|
||||||
Ui::GPClient *ui;
|
Ui::GPClient *ui;
|
||||||
QNetworkAccessManager *networkManager;
|
|
||||||
QNetworkReply *reply;
|
|
||||||
com::yuezk::qt::GPService *vpn;
|
com::yuezk::qt::GPService *vpn;
|
||||||
QSettings *settings;
|
|
||||||
|
QSystemTrayIcon *systemTrayIcon;
|
||||||
|
QMenu *contextMenu;
|
||||||
|
QAction *openAction;
|
||||||
|
QAction *connectAction;
|
||||||
|
QAction *quitAction;
|
||||||
|
|
||||||
|
GPGateway gateway;
|
||||||
|
PortalConfigResponse portalConfig;
|
||||||
|
|
||||||
|
QString portal() const;
|
||||||
|
|
||||||
void initVpnStatus();
|
void initVpnStatus();
|
||||||
void moveCenter();
|
void doConnect();
|
||||||
void updateConnectionStatus(QString status);
|
void updateConnectionStatus(const VpnStatus &status);
|
||||||
void doAuth(const QString portal);
|
|
||||||
void samlLogin(const QString loginUrl, const QString html = "");
|
void portalLogin(const QString& portal);
|
||||||
|
void gatewayLogin() const;
|
||||||
|
|
||||||
|
void quit();
|
||||||
};
|
};
|
||||||
#endif // GPCLIENT_H
|
#endif // GPCLIENT_H
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>GP VPN Client</string>
|
<string>GlobalProtect OpenConnect</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="resources.qrc">
|
<iconset resource="resources.qrc">
|
||||||
@ -113,6 +113,12 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Connect</string>
|
<string>Connect</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
38
GPClient/gpgateway.cpp
Normal file
38
GPClient/gpgateway.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#include "gpgateway.h"
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (_priorityRules.contains(ruleName)) {
|
||||||
|
return _priorityRules.value(ruleName);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
26
GPClient/gpgateway.h
Normal file
26
GPClient/gpgateway.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef GPGATEWAY_H
|
||||||
|
#define GPGATEWAY_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString _name;
|
||||||
|
QString _address;
|
||||||
|
QMap<QString, int> _priorityRules;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GPGATEWAY_H
|
118
GPClient/gphelper.cpp
Normal file
118
GPClient/gphelper.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
SAMLLoginWindow* gpclient::helper::samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl)
|
||||||
|
{
|
||||||
|
SAMLLoginWindow *loginWindow = new SAMLLoginWindow;
|
||||||
|
|
||||||
|
if (samlMethod == "POST") {
|
||||||
|
loginWindow->login(preloginUrl, samlRequest);
|
||||||
|
} else if (samlMethod == "REDIRECT") {
|
||||||
|
loginWindow->login(samlRequest);
|
||||||
|
} else {
|
||||||
|
PLOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return loginWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPGateway &gpclient::helper::filterPreferredGateway(QList<GPGateway> &gateways, 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("GlobalProtect");
|
||||||
|
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);
|
||||||
|
}
|
43
GPClient/gphelper.h
Normal file
43
GPClient/gphelper.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
SAMLLoginWindow *samlLogin(QString samlMethod, QString samlRequest, QString preloginUrl);
|
||||||
|
|
||||||
|
GPGateway& filterPreferredGateway(QList<GPGateway> &gateways, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GPHELPER_H
|
54
GPClient/loginparams.cpp
Normal file
54
GPClient/loginparams.cpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#include "loginparams.h"
|
||||||
|
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
LoginParams::LoginParams()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
44
GPClient/loginparams.h
Normal file
44
GPClient/loginparams.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#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 {
|
||||||
|
{"prot", QUrl::toPercentEncoding("https:")},
|
||||||
|
{"server", ""},
|
||||||
|
{"inputSrc", ""},
|
||||||
|
{"jnlpReady", "jnlpReady"},
|
||||||
|
{"user", ""},
|
||||||
|
{"passwd", ""},
|
||||||
|
{"computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())},
|
||||||
|
{"ok", "Login"},
|
||||||
|
{"direct", "yes"},
|
||||||
|
{"clientVer", "4100"},
|
||||||
|
{"os-version", QUrl::toPercentEncoding(QSysInfo::prettyProductName())},
|
||||||
|
{"clientos", "Linux"},
|
||||||
|
{"portal-userauthcookie", ""},
|
||||||
|
{"portal-prelogonuserauthcookie", ""},
|
||||||
|
{"prelogin-cookie", ""},
|
||||||
|
{"ipv6-support", "yes"}
|
||||||
|
};
|
||||||
|
|
||||||
|
void updateQueryItem(const QString &key, const QString &value);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LOGINPARAMS_H
|
@ -2,17 +2,32 @@
|
|||||||
#include "gpclient.h"
|
#include "gpclient.h"
|
||||||
#include "enhancedwebview.h"
|
#include "enhancedwebview.h"
|
||||||
|
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <plog/Log.h>
|
||||||
|
#include <plog/Appenders/ColorConsoleAppender.h>
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
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));
|
QString port = QString::fromLocal8Bit(qgetenv(ENV_CDP_PORT));
|
||||||
|
|
||||||
if (port == "") {
|
if (port == "") {
|
||||||
qputenv(ENV_CDP_PORT, "12315");
|
qputenv(ENV_CDP_PORT, "12315");
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleApplication app(argc, argv);
|
SingleApplication app(argc, argv);
|
||||||
GPClient w;
|
GPClient w;
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::raise);
|
QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activiate);
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
62
GPClient/normalloginwindow.cpp
Normal file
62
GPClient/normalloginwindow.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include "normalloginwindow.h"
|
||||||
|
#include "ui_normalloginwindow.h"
|
||||||
|
|
||||||
|
#include <QCloseEvent>
|
||||||
|
|
||||||
|
NormalLoginWindow::NormalLoginWindow(QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::NormalLoginWindow)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setFixedSize(width(), height());
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
37
GPClient/normalloginwindow.h
Normal file
37
GPClient/normalloginwindow.h
Normal 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
|
148
GPClient/normalloginwindow.ui
Normal file
148
GPClient/normalloginwindow.ui
Normal 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>
|
190
GPClient/portalauthenticator.cpp
Normal file
190
GPClient/portalauthenticator.cpp
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#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);
|
||||||
|
|
||||||
|
normalLoginWindow->exec();
|
||||||
|
delete normalLoginWindow;
|
||||||
|
normalLoginWindow = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalAuthenticator::onPerformNormalLogin(const QString &username, const QString &password)
|
||||||
|
{
|
||||||
|
normalLoginWindow->setProcessing(true);
|
||||||
|
fetchConfig(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalAuthenticator::onLoginWindowRejected()
|
||||||
|
{
|
||||||
|
emitFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalAuthenticator::samlAuth()
|
||||||
|
{
|
||||||
|
PLOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod();
|
||||||
|
|
||||||
|
SAMLLoginWindow *loginWindow = samlLogin(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl);
|
||||||
|
|
||||||
|
if (!loginWindow) {
|
||||||
|
openMessageBox("SAML Login failed for portal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(loginWindow, &SAMLLoginWindow::success, this, &PortalAuthenticator::onSAMLLoginSuccess);
|
||||||
|
connect(loginWindow, &SAMLLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalAuthenticator::emitFail(const QString& msg)
|
||||||
|
{
|
||||||
|
emit fail(msg);
|
||||||
|
}
|
52
GPClient/portalauthenticator.h
Normal file
52
GPClient/portalauthenticator.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#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&);
|
||||||
|
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 onSAMLLoginSuccess(const QMap<QString, QString> &samlResult);
|
||||||
|
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
|
145
GPClient/portalconfigresponse.cpp
Normal file
145
GPClient/portalconfigresponse.cpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#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::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.setGateways(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 gateway;
|
||||||
|
QString address = xmlReader.attributes().value("name").toString();
|
||||||
|
gateway.setAddress(address);
|
||||||
|
gateway.setPriorityRules(parsePriorityRules(xmlReader));
|
||||||
|
gateway.setName(parseGatewayName(xmlReader));
|
||||||
|
gateways.append(gateway);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortalConfigResponse::setGateways(const QList<GPGateway> &gateways)
|
||||||
|
{
|
||||||
|
_gateways = gateways;
|
||||||
|
}
|
50
GPClient/portalconfigresponse.h
Normal file
50
GPClient/portalconfigresponse.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef PORTALCONFIGRESPONSE_H
|
||||||
|
#define PORTALCONFIGRESPONSE_H
|
||||||
|
|
||||||
|
#include "gpgateway.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QList>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
|
class PortalConfigResponse
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
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 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);
|
||||||
|
void setGateways(const QList<GPGateway>& gateways);
|
||||||
|
|
||||||
|
static QList<GPGateway> parseGateways(QXmlStreamReader &xmlReader);
|
||||||
|
static QMap<QString, int> parsePriorityRules(QXmlStreamReader &xmlReader);
|
||||||
|
static QString parseGatewayName(QXmlStreamReader &xmlReader);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PORTALCONFIGRESPONSE_H
|
97
GPClient/preloginresponse.cpp
Normal file
97
GPClient/preloginresponse.cpp
Normal 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);
|
||||||
|
}
|
41
GPClient/preloginresponse.h
Normal file
41
GPClient/preloginresponse.h
Normal 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
|
@ -1,19 +1,24 @@
|
|||||||
#include "samlloginwindow.h"
|
#include "samlloginwindow.h"
|
||||||
|
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <plog/Log.h>
|
||||||
|
#include <QWebEngineProfile>
|
||||||
|
|
||||||
SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
|
SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle("SAML Login");
|
setWindowTitle("GlobalProtect SAML Login");
|
||||||
resize(610, 406);
|
resize(700, 550);
|
||||||
|
|
||||||
QVBoxLayout *verticalLayout = new QVBoxLayout(this);
|
QVBoxLayout *verticalLayout = new QVBoxLayout(this);
|
||||||
webView = new EnhancedWebView(this);
|
webView = new EnhancedWebView(this);
|
||||||
webView->setUrl(QUrl("about:blank"));
|
webView->setUrl(QUrl("about:blank"));
|
||||||
|
// webView->page()->profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
|
||||||
verticalLayout->addWidget(webView);
|
verticalLayout->addWidget(webView);
|
||||||
|
|
||||||
webView->initialize();
|
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()
|
SAMLLoginWindow::~SAMLLoginWindow()
|
||||||
@ -29,7 +34,7 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event)
|
|||||||
|
|
||||||
void SAMLLoginWindow::login(QString url, QString html)
|
void SAMLLoginWindow::login(QString url, QString html)
|
||||||
{
|
{
|
||||||
if (html == "") {
|
if (html.isEmpty()) {
|
||||||
webView->load(QUrl(url));
|
webView->load(QUrl(url));
|
||||||
} else {
|
} else {
|
||||||
webView->setHtml(html, url);
|
webView->setHtml(html, url);
|
||||||
@ -47,17 +52,24 @@ void SAMLLoginWindow::onResponseReceived(QJsonObject params)
|
|||||||
QJsonObject response = params.value("response").toObject();
|
QJsonObject response = params.value("response").toObject();
|
||||||
QJsonObject headers = response.value("headers").toObject();
|
QJsonObject headers = response.value("headers").toObject();
|
||||||
|
|
||||||
foreach (const QString& key, headers.keys()) {
|
const QString username = headers.value("saml-username").toString();
|
||||||
if (key.startsWith("saml-") || key == "prelogin-cookie" || key == "portal-userauthcookie") {
|
const QString preloginCookie = headers.value("prelogin-cookie").toString();
|
||||||
samlResult.insert(key, headers.value(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the SAML result
|
if (!username.isEmpty() && !preloginCookie.isEmpty()) {
|
||||||
if (samlResult.contains("saml-username")
|
samlResult.insert("username", username);
|
||||||
&& (samlResult.contains("prelogin-cookie") || samlResult.contains("portal-userauthcookie"))) {
|
samlResult.insert("preloginCookie", preloginCookie);
|
||||||
samlResult.insert("server", QUrl(response.value("url").toString()).authority());
|
}
|
||||||
emit success(samlResult);
|
}
|
||||||
accept();
|
|
||||||
|
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 {
|
||||||
|
open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "enhancedwebview.h"
|
#include "enhancedwebview.h"
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QJsonObject>
|
#include <QMap>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
|
|
||||||
class SAMLLoginWindow : public QDialog
|
class SAMLLoginWindow : public QDialog
|
||||||
@ -18,14 +18,15 @@ public:
|
|||||||
void login(QString url, QString html = "");
|
void login(QString url, QString html = "");
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void success(QJsonObject samlResult);
|
void success(QMap<QString, QString> samlResult);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onResponseReceived(QJsonObject params);
|
void onResponseReceived(QJsonObject params);
|
||||||
|
void onLoadFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EnhancedWebView *webView;
|
EnhancedWebView *webView;
|
||||||
QJsonObject samlResult;
|
QMap<QString, QString> samlResult;
|
||||||
|
|
||||||
void closeEvent(QCloseEvent *event);
|
void closeEvent(QCloseEvent *event);
|
||||||
};
|
};
|
||||||
|
@ -66,13 +66,10 @@ void GPService::connect(QString server, QString username, QString passwd)
|
|||||||
args << QCoreApplication::arguments().mid(1)
|
args << QCoreApplication::arguments().mid(1)
|
||||||
<< "--protocol=gp"
|
<< "--protocol=gp"
|
||||||
<< "-u" << username
|
<< "-u" << username
|
||||||
<< "--passwd-on-stdin"
|
<< "-C" << passwd
|
||||||
<< "--timestamp"
|
|
||||||
<< server;
|
<< server;
|
||||||
|
|
||||||
openconnect->start(bin, args);
|
openconnect->start(bin, args);
|
||||||
openconnect->write(passwd.toUtf8());
|
|
||||||
openconnect->closeWriteChannel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPService::disconnect()
|
void GPService::disconnect()
|
||||||
@ -130,6 +127,5 @@ void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|||||||
|
|
||||||
void GPService::log(QString msg)
|
void GPService::log(QString msg)
|
||||||
{
|
{
|
||||||
qInfo() << msg;
|
|
||||||
emit logAvailable(msg);
|
emit logAvailable(msg);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
Description=GlobalProtect openconnect DBus service
|
Description=GlobalProtect openconnect DBus service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Environment=LC_ALL=en_US
|
||||||
Type=dbus
|
Type=dbus
|
||||||
BusName=com.yuezk.qt.GPService
|
BusName=com.yuezk.qt.GPService
|
||||||
ExecStart=/usr/bin/gpservice
|
ExecStart=/usr/bin/gpservice
|
||||||
|
@ -5,6 +5,12 @@ A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Q
|
|||||||
<img src="screenshot.png">
|
<img src="screenshot.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Openconnect v8.x
|
- Openconnect v8.x
|
||||||
|
1
plog
Submodule
1
plog
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit fda4a26c26b2d1b2beb68d7b92b56950ec2b8ad2
|
Loading…
Reference in New Issue
Block a user