diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f49391..b90ffd7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,64 +66,21 @@ jobs: with: name: snapshot-source-code 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 - run: | - sudo apt update - sudo apt install debmake debhelper cmake \ - libqt5websockets5-dev qtbase5-dev qtwebengine5-dev - - - name: Build deb package for 18.04 + - name: Extract source code run: | cd $GITHUB_WORKSPACE/artifacts - mkdir build-18.04 - cp *.tar.gz build-18.04 && cd build-18.04 + mkdir deb-build && cp *.tar.gz deb-build && cd deb-build 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: if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} needs: snapshot-archive-all @@ -233,62 +190,19 @@ jobs: name: release-source-code path: artifacts - - name: Import GPG key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + - name: Extract source code + run: | + 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: + repository: 'ppa:yuezk/globalprotect-openconnect' gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} - - - 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 + gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} + pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' release-aur: if: startsWith(github.ref, 'refs/tags/v') diff --git a/GPClient/CMakeLists.txt b/GPClient/CMakeLists.txt index 5387aa4..3bfe1cb 100644 --- a/GPClient/CMakeLists.txt +++ b/GPClient/CMakeLists.txt @@ -33,6 +33,9 @@ add_executable(gpclient gpclient.ui normalloginwindow.ui settingsdialog.ui + challengedialog.h + challengedialog.cpp + challengedialog.ui vpn_dbus.cpp vpn_json.cpp resources.qrc diff --git a/GPClient/challengedialog.cpp b/GPClient/challengedialog.cpp new file mode 100644 index 0000000..bf842e7 --- /dev/null +++ b/GPClient/challengedialog.cpp @@ -0,0 +1,38 @@ +#include +#include + +#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); + } +} diff --git a/GPClient/challengedialog.h b/GPClient/challengedialog.h new file mode 100644 index 0000000..bd6b95d --- /dev/null +++ b/GPClient/challengedialog.h @@ -0,0 +1,28 @@ +#ifndef CHALLENGEDIALOG_H +#define CHALLENGEDIALOG_H + +#include + +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 diff --git a/GPClient/challengedialog.ui b/GPClient/challengedialog.ui new file mode 100644 index 0000000..6aafa22 --- /dev/null +++ b/GPClient/challengedialog.ui @@ -0,0 +1,111 @@ + + + ChallengeDialog + + + + 0 + 0 + 405 + 200 + + + + GlobalProtect Challenge + + + true + + + + + + + + + 14 + 50 + false + + + + Sign In + + + + + + + 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): + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + QLineEdit::Password + + + + + + + Qt::LeftToRight + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + + buttonBox + accepted() + ChallengeDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ChallengeDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/GPClient/gatewayauthenticator.cpp b/GPClient/gatewayauthenticator.cpp index 60e98de..37611ee 100644 --- a/GPClient/gatewayauthenticator.cpp +++ b/GPClient/gatewayauthenticator.cpp @@ -1,14 +1,17 @@ #include +#include +#include #include #include "gatewayauthenticator.h" #include "gphelper.h" #include "loginparams.h" #include "preloginresponse.h" +#include "challengedialog.h" using namespace gpclient::helper; -GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, const GatewayAuthenticatorParams params) +GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params) : QObject() , gateway(gateway) , params(params) @@ -33,6 +36,7 @@ void GatewayAuthenticator::authenticate() loginParams.setUser(params.username()); loginParams.setPassword(params.password()); loginParams.setUserAuthCookie(params.userAuthCookie()); + loginParams.setInputStr(params.inputStr()); login(loginParams); } @@ -48,10 +52,10 @@ void GatewayAuthenticator::login(const LoginParams &loginParams) void GatewayAuthenticator::onLoginFinished() { QNetworkReply *reply = qobject_cast(sender()); - QByteArray response; + QByteArray response = reply->readAll(); - if (reply->error() || (response = reply->readAll()).contains("Authentication failure")) { - PLOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl).arg(reply->errorString()); + if (reply->error() || response.contains("Authentication failure")) { + PLOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl, reply->errorString()); if (normalLoginWindow) { normalLoginWindow->setProcessing(false); @@ -62,6 +66,13 @@ void GatewayAuthenticator::onLoginFinished() return; } + // 2FA + if (response.contains("Challenge")) { + PLOGI << "The server need input the challenge..."; + showChallenge(response); + return; + } + if (normalLoginWindow) { normalLoginWindow->close(); } @@ -83,7 +94,7 @@ void GatewayAuthenticator::onPreloginFinished() QNetworkReply *reply = qobject_cast(sender()); 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."); return; @@ -98,7 +109,7 @@ void GatewayAuthenticator::onPreloginFinished() } 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())); + PLOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl, QString::fromUtf8(response.rawResponse())); emit fail("Unknown response for gateway prelogin interface."); } @@ -107,7 +118,7 @@ void GatewayAuthenticator::onPreloginFinished() 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->setPortalAddress(gateway); @@ -128,11 +139,10 @@ void GatewayAuthenticator::onPerformNormalLogin(const QString &username, const Q PLOGI << "Start to perform normal login..."; normalLoginWindow->setProcessing(true); - LoginParams loginParams { params.clientos() }; - loginParams.setUser(username); - loginParams.setPassword(password); + params.setUsername(username); + params.setPassword(password); - login(loginParams); + authenticate(); } void GatewayAuthenticator::onLoginWindowRejected() @@ -179,3 +189,38 @@ void GatewayAuthenticator::onSAMLLoginFail(const QString 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(); +} diff --git a/GPClient/gatewayauthenticator.h b/GPClient/gatewayauthenticator.h index c150a88..df92a31 100644 --- a/GPClient/gatewayauthenticator.h +++ b/GPClient/gatewayauthenticator.h @@ -4,6 +4,7 @@ #include #include "normalloginwindow.h" +#include "challengedialog.h" #include "loginparams.h" #include "gatewayauthenticatorparams.h" @@ -11,7 +12,7 @@ class GatewayAuthenticator : public QObject { Q_OBJECT public: - explicit GatewayAuthenticator(const QString& gateway, const GatewayAuthenticatorParams params); + explicit GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params); ~GatewayAuthenticator(); void authenticate(); @@ -31,16 +32,18 @@ private slots: private: QString gateway; - const GatewayAuthenticatorParams params; + GatewayAuthenticatorParams params; QString preloginUrl; QString loginUrl; NormalLoginWindow *normalLoginWindow{ nullptr }; + ChallengeDialog *challengeDialog{ nullptr }; void login(const LoginParams& loginParams); void doAuth(); void normalAuth(QString labelUsername, QString labelPassword, QString authMessage); void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = ""); + void showChallenge(const QString &responseText); }; #endif // GATEWAYAUTHENTICATOR_H diff --git a/GPClient/gatewayauthenticatorparams.cpp b/GPClient/gatewayauthenticatorparams.cpp index 4db1a74..80fce27 100644 --- a/GPClient/gatewayauthenticatorparams.cpp +++ b/GPClient/gatewayauthenticatorparams.cpp @@ -55,3 +55,13 @@ void GatewayAuthenticatorParams::setClientos(const QString &newClientos) m_clientos = newClientos; } +const QString &GatewayAuthenticatorParams::inputStr() const +{ + return m_inputStr; +} + +void GatewayAuthenticatorParams::setInputStr(const QString &inputStr) +{ + m_inputStr = inputStr; +} + diff --git a/GPClient/gatewayauthenticatorparams.h b/GPClient/gatewayauthenticatorparams.h index 72d9176..ec48fc1 100644 --- a/GPClient/gatewayauthenticatorparams.h +++ b/GPClient/gatewayauthenticatorparams.h @@ -24,11 +24,15 @@ public: const QString &clientos() const; void setClientos(const QString &newClientos); + const QString &inputStr() const; + void setInputStr(const QString &inputStr); + private: QString m_username; QString m_password; QString m_userAuthCookie; QString m_clientos; + QString m_inputStr; }; #endif // GATEWAYAUTHENTICATORPARAMS_H diff --git a/GPClient/loginparams.cpp b/GPClient/loginparams.cpp index 5b59fe3..3126231 100644 --- a/GPClient/loginparams.cpp +++ b/GPClient/loginparams.cpp @@ -6,7 +6,7 @@ LoginParams::LoginParams(const QString clientos) { params.addQueryItem("prot", QUrl::toPercentEncoding("https:")); params.addQueryItem("server", ""); - params.addQueryItem("inputSrc", ""); + params.addQueryItem("inputStr", ""); params.addQueryItem("jnlpReady", "jnlpReady"); params.addQueryItem("user", ""); params.addQueryItem("passwd", ""); @@ -61,6 +61,11 @@ void LoginParams::setPreloginCookie(const QString cookie) updateQueryItem("prelogin-cookie", cookie); } +void LoginParams::setInputStr(const QString inputStr) +{ + updateQueryItem("inputStr", inputStr); +} + QByteArray LoginParams::toUtf8() const { return params.toString().toUtf8(); diff --git a/GPClient/loginparams.h b/GPClient/loginparams.h index 87d3d10..1660ad5 100644 --- a/GPClient/loginparams.h +++ b/GPClient/loginparams.h @@ -15,6 +15,7 @@ public: void setUserAuthCookie(const QString cookie); void setPrelogonAuthCookie(const QString cookie); void setPreloginCookie(const QString cookie); + void setInputStr(const QString inputStr); QByteArray toUtf8() const; diff --git a/GPClient/portalauthenticator.cpp b/GPClient/portalauthenticator.cpp index 2958eee..30ed6b1 100644 --- a/GPClient/portalauthenticator.cpp +++ b/GPClient/portalauthenticator.cpp @@ -41,7 +41,7 @@ void PortalAuthenticator::onPreloginFinished() QNetworkReply *reply = qobject_cast(sender()); 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."); delete reply; return; diff --git a/VERSION b/VERSION index d0149fe..8c9698a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.4 +1.3.4 \ No newline at end of file diff --git a/debian/source/format b/debian/source/format index 163aaf8..9f67427 100644 --- a/debian/source/format +++ b/debian/source/format @@ -1 +1 @@ -3.0 (quilt) +3.0 (native) \ No newline at end of file