Add 2FA support (#112)

This commit is contained in:
Kevin Yue 2021-12-20 22:20:02 +08:00 committed by GitHub
parent 9d6ec84c14
commit 8f27c92e7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 286 additions and 124 deletions

View File

@ -66,64 +66,21 @@ jobs:
with: with:
name: snapshot-source-code name: snapshot-source-code
path: artifacts path: artifacts
- name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v4
with:
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }}
- name: Install dependencies - name: Extract source code
run: |
sudo apt update
sudo apt install debmake debhelper cmake \
libqt5websockets5-dev qtbase5-dev qtwebengine5-dev
- name: Build deb package for 18.04
run: | run: |
cd $GITHUB_WORKSPACE/artifacts cd $GITHUB_WORKSPACE/artifacts
mkdir build-18.04 mkdir deb-build && cp *.tar.gz deb-build && cd deb-build
cp *.tar.gz build-18.04 && cd build-18.04
tar xf *.tar.gz tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 18.04
- name: Build deb package for 20.04
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-20.04
cp *.tar.gz build-20.04 && cd build-20.04
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 20.04
- name: Build deb package for 21.04
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-21.04
cp *.tar.gz build-21.04 && cd build-21.04
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 21.04
- name: Build deb package for 21.10
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-21.10
cp *.tar.gz build-21.10 && cd build-21.10
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 21.10
- name: Publish PPA
uses: yuezk/publish-ppa-package@develop
with:
repository: 'ppa:yuezk/globalprotect-openconnect-snapshot'
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }}
pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/'
snapshot-aur: snapshot-aur:
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }}
needs: snapshot-archive-all needs: snapshot-archive-all
@ -233,62 +190,19 @@ jobs:
name: release-source-code name: release-source-code
path: artifacts path: artifacts
- name: Import GPG key - name: Extract source code
id: import_gpg run: |
uses: crazy-max/ghaction-import-gpg@v4 cd $GITHUB_WORKSPACE/artifacts
mkdir deb-build && cp *.tar.gz deb-build && cd deb-build
tar xf *.tar.gz
- name: Publish PPA
uses: yuezk/publish-ppa-package@develop
with: with:
repository: 'ppa:yuezk/globalprotect-openconnect'
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }}
pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/'
- name: Install dependencies
run: |
sudo apt update
sudo apt install debmake debhelper cmake \
libqt5websockets5-dev qtbase5-dev qtwebengine5-dev
- name: Build deb package for 18.04
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-18.04
cp *.tar.gz build-18.04 && cd build-18.04
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 18.04 --stable
- name: Build deb package for 20.04
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-20.04
cp *.tar.gz build-20.04 && cd build-20.04
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 20.04 --stable
- name: Build deb package for 21.04
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-21.04
cp *.tar.gz build-21.04 && cd build-21.04
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 21.04 --stable
- name: Build deb package for 21.10
run: |
cd $GITHUB_WORKSPACE/artifacts
mkdir build-21.10
cp *.tar.gz build-21.10 && cd build-21.10
tar xf *.tar.gz
cd globalprotect-openconnect-*/
PPA_GPG_PASSPHRASE=${{ secrets.PPA_GPG_PASSPHRASE }} \
PPA_GPG_KEYID=${{ steps.import_gpg.outputs.keyid }} ./scripts/ppa-publish.sh 21.10 --stable
release-aur: release-aur:
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')

View File

@ -33,6 +33,9 @@ add_executable(gpclient
gpclient.ui gpclient.ui
normalloginwindow.ui normalloginwindow.ui
settingsdialog.ui settingsdialog.ui
challengedialog.h
challengedialog.cpp
challengedialog.ui
vpn_dbus.cpp vpn_dbus.cpp
vpn_json.cpp vpn_json.cpp
resources.qrc resources.qrc

View File

@ -0,0 +1,38 @@
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QPushButton>
#include "challengedialog.h"
#include "ui_challengedialog.h"
ChallengeDialog::ChallengeDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ChallengeDialog)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true);
}
ChallengeDialog::~ChallengeDialog()
{
delete ui;
}
void ChallengeDialog::setMessage(const QString &message)
{
ui->challengeMessage->setText(message);
}
const QString ChallengeDialog::getChallenge()
{
return ui->challengeInput->text();
}
void ChallengeDialog::on_challengeInput_textChanged(const QString &value)
{
QPushButton *okBtn = ui->buttonBox->button(QDialogButtonBox::Ok);
if (value.isEmpty()) {
okBtn->setDisabled(true);
} else {
okBtn->setEnabled(true);
}
}

View File

@ -0,0 +1,28 @@
#ifndef CHALLENGEDIALOG_H
#define CHALLENGEDIALOG_H
#include <QDialog>
namespace Ui {
class ChallengeDialog;
}
class ChallengeDialog : public QDialog
{
Q_OBJECT
public:
explicit ChallengeDialog(QWidget *parent = nullptr);
~ChallengeDialog();
void setMessage(const QString &message);
const QString getChallenge();
private slots:
void on_challengeInput_textChanged(const QString &arg1);
private:
Ui::ChallengeDialog *ui;
};
#endif // CHALLENGEDIALOG_H

111
GPClient/challengedialog.ui Normal file
View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChallengeDialog</class>
<widget class="QDialog" name="ChallengeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>405</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>GlobalProtect Challenge</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Sign In</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="challengeMessage">
<property name="text">
<string>Duo two-factor login for [redacted] Enter a passcode or select one of the following options: 1. Duo Push to XXX-XXX-[redacted] 2. SMS passcodes to XXX-XXX-[redacted] Passcode or option (1-2): </string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="challengeInput">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ChallengeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ChallengeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,14 +1,17 @@
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QtCore/QRegularExpression>
#include <QtCore/QRegularExpressionMatch>
#include <plog/Log.h> #include <plog/Log.h>
#include "gatewayauthenticator.h" #include "gatewayauthenticator.h"
#include "gphelper.h" #include "gphelper.h"
#include "loginparams.h" #include "loginparams.h"
#include "preloginresponse.h" #include "preloginresponse.h"
#include "challengedialog.h"
using namespace gpclient::helper; using namespace gpclient::helper;
GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, const GatewayAuthenticatorParams params) GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params)
: QObject() : QObject()
, gateway(gateway) , gateway(gateway)
, params(params) , params(params)
@ -33,6 +36,7 @@ void GatewayAuthenticator::authenticate()
loginParams.setUser(params.username()); loginParams.setUser(params.username());
loginParams.setPassword(params.password()); loginParams.setPassword(params.password());
loginParams.setUserAuthCookie(params.userAuthCookie()); loginParams.setUserAuthCookie(params.userAuthCookie());
loginParams.setInputStr(params.inputStr());
login(loginParams); login(loginParams);
} }
@ -48,10 +52,10 @@ void GatewayAuthenticator::login(const LoginParams &loginParams)
void GatewayAuthenticator::onLoginFinished() void GatewayAuthenticator::onLoginFinished()
{ {
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
QByteArray response; QByteArray response = reply->readAll();
if (reply->error() || (response = reply->readAll()).contains("Authentication failure")) { if (reply->error() || response.contains("Authentication failure")) {
PLOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl).arg(reply->errorString()); PLOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl, reply->errorString());
if (normalLoginWindow) { if (normalLoginWindow) {
normalLoginWindow->setProcessing(false); normalLoginWindow->setProcessing(false);
@ -62,6 +66,13 @@ void GatewayAuthenticator::onLoginFinished()
return; return;
} }
// 2FA
if (response.contains("Challenge")) {
PLOGI << "The server need input the challenge...";
showChallenge(response);
return;
}
if (normalLoginWindow) { if (normalLoginWindow) {
normalLoginWindow->close(); normalLoginWindow->close();
} }
@ -83,7 +94,7 @@ void GatewayAuthenticator::onPreloginFinished()
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) { if (reply->error()) {
PLOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl).arg(reply->errorString()); PLOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl, reply->errorString());
emit fail("Error occurred on the gateway prelogin interface."); emit fail("Error occurred on the gateway prelogin interface.");
return; return;
@ -98,7 +109,7 @@ void GatewayAuthenticator::onPreloginFinished()
} else if (response.hasNormalAuthFields()) { } else if (response.hasNormalAuthFields()) {
normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage()); normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage());
} else { } else {
PLOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl).arg(QString::fromUtf8(response.rawResponse())); PLOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl, QString::fromUtf8(response.rawResponse()));
emit fail("Unknown response for gateway prelogin interface."); emit fail("Unknown response for gateway prelogin interface.");
} }
@ -107,7 +118,7 @@ void GatewayAuthenticator::onPreloginFinished()
void GatewayAuthenticator::normalAuth(QString labelUsername, QString labelPassword, QString authMessage) 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); PLOGI << QString("Trying to perform the normal login with %1 / %2 credentials").arg(labelUsername, labelPassword);
normalLoginWindow = new NormalLoginWindow; normalLoginWindow = new NormalLoginWindow;
normalLoginWindow->setPortalAddress(gateway); normalLoginWindow->setPortalAddress(gateway);
@ -128,11 +139,10 @@ void GatewayAuthenticator::onPerformNormalLogin(const QString &username, const Q
PLOGI << "Start to perform normal login..."; PLOGI << "Start to perform normal login...";
normalLoginWindow->setProcessing(true); normalLoginWindow->setProcessing(true);
LoginParams loginParams { params.clientos() }; params.setUsername(username);
loginParams.setUser(username); params.setPassword(password);
loginParams.setPassword(password);
login(loginParams); authenticate();
} }
void GatewayAuthenticator::onLoginWindowRejected() void GatewayAuthenticator::onLoginWindowRejected()
@ -179,3 +189,38 @@ void GatewayAuthenticator::onSAMLLoginFail(const QString msg)
{ {
emit fail(msg); emit fail(msg);
} }
void GatewayAuthenticator::showChallenge(const QString &responseText)
{
QRegularExpression re("\"(.*?)\";");
QRegularExpressionMatchIterator i = re.globalMatch(responseText);
i.next(); // Skip the status value
QString message = i.next().captured(1);
QString inputStr = i.next().captured(1);
// update the inputSrc field
params.setInputStr(inputStr);
challengeDialog = new ChallengeDialog;
challengeDialog->setMessage(message);
connect(challengeDialog, &ChallengeDialog::accepted, this, [this] {
params.setPassword(challengeDialog->getChallenge());
PLOGI << "Challenge submitted, try to re-authenticate...";
authenticate();
});
connect(challengeDialog, &ChallengeDialog::rejected, this, [this] {
if (normalLoginWindow) {
normalLoginWindow->close();
}
emit fail();
});
connect(challengeDialog, &ChallengeDialog::finished, this, [this] {
delete challengeDialog;
challengeDialog = nullptr;
});
challengeDialog->show();
}

View File

@ -4,6 +4,7 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include "normalloginwindow.h" #include "normalloginwindow.h"
#include "challengedialog.h"
#include "loginparams.h" #include "loginparams.h"
#include "gatewayauthenticatorparams.h" #include "gatewayauthenticatorparams.h"
@ -11,7 +12,7 @@ class GatewayAuthenticator : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit GatewayAuthenticator(const QString& gateway, const GatewayAuthenticatorParams params); explicit GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params);
~GatewayAuthenticator(); ~GatewayAuthenticator();
void authenticate(); void authenticate();
@ -31,16 +32,18 @@ private slots:
private: private:
QString gateway; QString gateway;
const GatewayAuthenticatorParams params; GatewayAuthenticatorParams params;
QString preloginUrl; QString preloginUrl;
QString loginUrl; QString loginUrl;
NormalLoginWindow *normalLoginWindow{ nullptr }; NormalLoginWindow *normalLoginWindow{ nullptr };
ChallengeDialog *challengeDialog{ nullptr };
void login(const LoginParams& loginParams); void login(const LoginParams& loginParams);
void doAuth(); void doAuth();
void normalAuth(QString labelUsername, QString labelPassword, QString authMessage); void normalAuth(QString labelUsername, QString labelPassword, QString authMessage);
void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = ""); void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = "");
void showChallenge(const QString &responseText);
}; };
#endif // GATEWAYAUTHENTICATOR_H #endif // GATEWAYAUTHENTICATOR_H

View File

@ -55,3 +55,13 @@ void GatewayAuthenticatorParams::setClientos(const QString &newClientos)
m_clientos = newClientos; m_clientos = newClientos;
} }
const QString &GatewayAuthenticatorParams::inputStr() const
{
return m_inputStr;
}
void GatewayAuthenticatorParams::setInputStr(const QString &inputStr)
{
m_inputStr = inputStr;
}

View File

@ -24,11 +24,15 @@ public:
const QString &clientos() const; const QString &clientos() const;
void setClientos(const QString &newClientos); void setClientos(const QString &newClientos);
const QString &inputStr() const;
void setInputStr(const QString &inputStr);
private: private:
QString m_username; QString m_username;
QString m_password; QString m_password;
QString m_userAuthCookie; QString m_userAuthCookie;
QString m_clientos; QString m_clientos;
QString m_inputStr;
}; };
#endif // GATEWAYAUTHENTICATORPARAMS_H #endif // GATEWAYAUTHENTICATORPARAMS_H

View File

@ -6,7 +6,7 @@ LoginParams::LoginParams(const QString clientos)
{ {
params.addQueryItem("prot", QUrl::toPercentEncoding("https:")); params.addQueryItem("prot", QUrl::toPercentEncoding("https:"));
params.addQueryItem("server", ""); params.addQueryItem("server", "");
params.addQueryItem("inputSrc", ""); params.addQueryItem("inputStr", "");
params.addQueryItem("jnlpReady", "jnlpReady"); params.addQueryItem("jnlpReady", "jnlpReady");
params.addQueryItem("user", ""); params.addQueryItem("user", "");
params.addQueryItem("passwd", ""); params.addQueryItem("passwd", "");
@ -61,6 +61,11 @@ void LoginParams::setPreloginCookie(const QString cookie)
updateQueryItem("prelogin-cookie", cookie); updateQueryItem("prelogin-cookie", cookie);
} }
void LoginParams::setInputStr(const QString inputStr)
{
updateQueryItem("inputStr", inputStr);
}
QByteArray LoginParams::toUtf8() const QByteArray LoginParams::toUtf8() const
{ {
return params.toString().toUtf8(); return params.toString().toUtf8();

View File

@ -15,6 +15,7 @@ public:
void setUserAuthCookie(const QString cookie); void setUserAuthCookie(const QString cookie);
void setPrelogonAuthCookie(const QString cookie); void setPrelogonAuthCookie(const QString cookie);
void setPreloginCookie(const QString cookie); void setPreloginCookie(const QString cookie);
void setInputStr(const QString inputStr);
QByteArray toUtf8() const; QByteArray toUtf8() const;

View File

@ -41,7 +41,7 @@ void PortalAuthenticator::onPreloginFinished()
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender()); QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error()) { if (reply->error()) {
PLOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl).arg(reply->errorString()); PLOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString());
emit preloginFailed("Error occurred on the portal prelogin interface."); emit preloginFailed("Error occurred on the portal prelogin interface.");
delete reply; delete reply;
return; return;

View File

@ -1 +1 @@
1.3.4 1.3.4

View File

@ -1 +1 @@
3.0 (quilt) 3.0 (native)