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"]
|
||||
path = singleapplication
|
||||
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.
|
||||
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,11 +41,20 @@ 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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
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 "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)
|
||||
, systemTrayIcon(new QSystemTrayIcon(parent))
|
||||
, contextMenu(new QMenu("GlobalProtect", parent))
|
||||
{
|
||||
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);
|
||||
|
||||
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();
|
||||
systemTrayIcon->show();
|
||||
}
|
||||
|
||||
GPClient::~GPClient()
|
||||
{
|
||||
delete ui;
|
||||
delete networkManager;
|
||||
delete reply;
|
||||
delete vpn;
|
||||
delete settings;
|
||||
delete systemTrayIcon;
|
||||
delete openAction;
|
||||
delete connectAction;
|
||||
delete quitAction;
|
||||
delete contextMenu;
|
||||
}
|
||||
|
||||
void GPClient::on_connectButton_clicked()
|
||||
{
|
||||
QString btnText = ui->connectButton->text();
|
||||
|
||||
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();
|
||||
}
|
||||
doConnect();
|
||||
}
|
||||
|
||||
void GPClient::preloginResultFinished()
|
||||
void GPClient::on_portalInput_returnPressed()
|
||||
{
|
||||
QNetworkReply::NetworkError err = reply->error();
|
||||
if (err) {
|
||||
qWarning() << "Prelogin request error: " << err;
|
||||
emit connectFailed();
|
||||
return;
|
||||
doConnect();
|
||||
}
|
||||
|
||||
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";
|
||||
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");
|
||||
}
|
||||
|
||||
void GPClient::updateConnectionStatus(QString status)
|
||||
{
|
||||
if (status == "not_connected") {
|
||||
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);
|
||||
} else if (status == "pending") {
|
||||
ui->portalInput->setReadOnly(false);
|
||||
|
||||
systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" });
|
||||
connectAction->setEnabled(true);
|
||||
connectAction->setText("Connect");
|
||||
break;
|
||||
case VpnStatus::pending:
|
||||
ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;");
|
||||
ui->connectButton->setText("Cancel");
|
||||
ui->connectButton->setDisabled(false);
|
||||
} else if (status == "connected") {
|
||||
ui->connectButton->setDisabled(true);
|
||||
ui->portalInput->setReadOnly(true);
|
||||
|
||||
systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" });
|
||||
connectAction->setEnabled(false);
|
||||
break;
|
||||
case VpnStatus::connected:
|
||||
ui->statusLabel->setText("Connected");
|
||||
ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;");
|
||||
ui->connectButton->setText("Disconnect");
|
||||
ui->connectButton->setDisabled(false);
|
||||
ui->portalInput->setReadOnly(true);
|
||||
|
||||
systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" });
|
||||
connectAction->setEnabled(true);
|
||||
connectAction->setText("Disconnect");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GPClient::onVPNConnected()
|
||||
{
|
||||
updateConnectionStatus("connected");
|
||||
updateConnectionStatus(VpnStatus::connected);
|
||||
}
|
||||
|
||||
void GPClient::onVPNDisconnected()
|
||||
{
|
||||
updateConnectionStatus("not_connected");
|
||||
updateConnectionStatus(VpnStatus::disconnected);
|
||||
}
|
||||
|
||||
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() {
|
||||
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::doConnect()
|
||||
{
|
||||
QDesktopWidget *desktop = QApplication::desktop();
|
||||
const QString btnText = ui->connectButton->text();
|
||||
const QString portal = this->portal();
|
||||
|
||||
int screenWidth, width;
|
||||
int screenHeight, height;
|
||||
int x, y;
|
||||
QSize windowSize;
|
||||
|
||||
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);
|
||||
if (portal.isEmpty()) {
|
||||
activiate();
|
||||
return;
|
||||
}
|
||||
|
||||
void GPClient::doAuth(const QString portal)
|
||||
if (btnText.endsWith("Connect")) {
|
||||
settings::save("portal", portal);
|
||||
ui->statusLabel->setText("Authenticating...");
|
||||
updateConnectionStatus(VpnStatus::pending);
|
||||
|
||||
// Perform the portal login
|
||||
portalLogin(portal);
|
||||
} 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(const QString& portal)
|
||||
{
|
||||
const QString preloginUrl = "https://" + portal + "/ssl-vpn/prelogin.esp";
|
||||
reply = networkManager->post(QNetworkRequest(preloginUrl), (QByteArray) nullptr);
|
||||
connect(reply, &QNetworkReply::finished, this, &GPClient::preloginResultFinished);
|
||||
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);
|
||||
|
||||
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);
|
||||
QObject::connect(loginWindow, &SAMLLoginWindow::rejected, this, &GPClient::connectFailed);
|
||||
|
||||
loginWindow->login(loginUrl, html);
|
||||
loginWindow->exec();
|
||||
delete loginWindow;
|
||||
gatewayLogin();
|
||||
}
|
||||
|
||||
void GPClient::onPortalPreloginFail()
|
||||
{
|
||||
PLOGI << "Portal prelogin failed, try to preform login on the the gateway interface...";
|
||||
|
||||
// Set the gateway address to portal input
|
||||
gateway.setAddress(portal());
|
||||
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
|
||||
|
||||
#include "gpservice_interface.h"
|
||||
#include "portalconfigresponse.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMenu>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class GPClient; }
|
||||
@ -17,31 +19,53 @@ class GPClient : public QMainWindow
|
||||
public:
|
||||
GPClient(QWidget *parent = nullptr);
|
||||
~GPClient();
|
||||
|
||||
signals:
|
||||
void connectFailed();
|
||||
void activiate();
|
||||
|
||||
private slots:
|
||||
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 onVPNDisconnected();
|
||||
void onVPNLogAvailable(QString log);
|
||||
|
||||
void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
|
||||
private:
|
||||
enum class VpnStatus
|
||||
{
|
||||
disconnected,
|
||||
pending,
|
||||
connected
|
||||
};
|
||||
|
||||
Ui::GPClient *ui;
|
||||
QNetworkAccessManager *networkManager;
|
||||
QNetworkReply *reply;
|
||||
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 moveCenter();
|
||||
void updateConnectionStatus(QString status);
|
||||
void doAuth(const QString portal);
|
||||
void samlLogin(const QString loginUrl, const QString html = "");
|
||||
void doConnect();
|
||||
void updateConnectionStatus(const VpnStatus &status);
|
||||
|
||||
void portalLogin(const QString& portal);
|
||||
void gatewayLogin() const;
|
||||
|
||||
void quit();
|
||||
};
|
||||
#endif // GPCLIENT_H
|
||||
|
@ -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>
|
||||
|
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 "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();
|
||||
}
|
||||
|
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 <QVBoxLayout>
|
||||
#include <plog/Log.h>
|
||||
#include <QWebEngineProfile>
|
||||
|
||||
SAMLLoginWindow::SAMLLoginWindow(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("SAML Login");
|
||||
resize(610, 406);
|
||||
setWindowTitle("GlobalProtect SAML Login");
|
||||
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()
|
||||
@ -29,7 +34,7 @@ void SAMLLoginWindow::closeEvent(QCloseEvent *event)
|
||||
|
||||
void SAMLLoginWindow::login(QString url, QString html)
|
||||
{
|
||||
if (html == "") {
|
||||
if (html.isEmpty()) {
|
||||
webView->load(QUrl(url));
|
||||
} else {
|
||||
webView->setHtml(html, url);
|
||||
@ -47,17 +52,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();
|
||||
|
||||
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.contains("saml-username")
|
||||
&& (samlResult.contains("prelogin-cookie") || samlResult.contains("portal-userauthcookie"))) {
|
||||
samlResult.insert("server", QUrl(response.value("url").toString()).authority());
|
||||
if (!samlResult.value("username").isEmpty() && !samlResult.value("preloginCookie").isEmpty()) {
|
||||
emit success(samlResult);
|
||||
accept();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "enhancedwebview.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QCloseEvent>
|
||||
|
||||
class SAMLLoginWindow : public QDialog
|
||||
@ -18,14 +18,15 @@ public:
|
||||
void login(QString url, QString html = "");
|
||||
|
||||
signals:
|
||||
void success(QJsonObject samlResult);
|
||||
void success(QMap<QString, QString> samlResult);
|
||||
|
||||
private slots:
|
||||
void onResponseReceived(QJsonObject params);
|
||||
void onLoadFinished();
|
||||
|
||||
private:
|
||||
EnhancedWebView *webView;
|
||||
QJsonObject samlResult;
|
||||
QMap<QString, QString> samlResult;
|
||||
|
||||
void closeEvent(QCloseEvent *event);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
Description=GlobalProtect openconnect DBus service
|
||||
|
||||
[Service]
|
||||
Environment=LC_ALL=en_US
|
||||
Type=dbus
|
||||
BusName=com.yuezk.qt.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">
|
||||
</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
|
||||
|
||||
- Openconnect v8.x
|
||||
|
1
plog
Submodule
1
plog
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit fda4a26c26b2d1b2beb68d7b92b56950ec2b8ad2
|
Loading…
Reference in New Issue
Block a user