diff --git a/providers/ghprovider/ghdialog.h b/providers/ghprovider/ghdialog.h --- a/providers/ghprovider/ghdialog.h +++ b/providers/ghprovider/ghdialog.h @@ -74,6 +74,11 @@ */ void authorizeResponse(const QByteArray &id, const QByteArray &token); + /** + * Handle a two factor response from GitHub during the authorization process. + */ + void twoFactorResponse(); + /// Sync the user's groups. void syncUser(); diff --git a/providers/ghprovider/ghdialog.cpp b/providers/ghprovider/ghdialog.cpp --- a/providers/ghprovider/ghdialog.cpp +++ b/providers/ghprovider/ghdialog.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -108,6 +109,8 @@ Resource *rs = m_account->resource(); rs->authenticate(dlg.username(), dlg.password()); + connect(rs, &Resource::twoFactorRequested, + this, &Dialog::twoFactorResponse); connect(rs, &Resource::authenticated, this, &Dialog::authorizeResponse); } @@ -131,6 +134,15 @@ syncUser(); } +void Dialog::twoFactorResponse() +{ + auto code = QInputDialog::getText(this, i18n("Authentication Code"), i18n("Authentication Code")); + Resource* rs = m_account->resource(); + disconnect(rs, &Resource::twoFactorRequested, this, &Dialog::twoFactorResponse); + + rs->twoFactorAuthenticate(code); +} + void Dialog::syncUser() { Resource *rs = m_account->resource(); diff --git a/providers/ghprovider/ghresource.h b/providers/ghprovider/ghresource.h --- a/providers/ghprovider/ghresource.h +++ b/providers/ghprovider/ghresource.h @@ -83,6 +83,14 @@ void authenticate(const QString &name, const QString &password); /** + * Authenticate the current user with the two factor authentication code. + * Must be called after a call to authenticate. + * + * @param code The two factor authentication code. + */ + void twoFactorAuthenticate(const QString &code); + + /** * Revoke an access to the Github API (a.k.a. log out the current user). * * @param id The id of the currently valid authorization. @@ -129,6 +137,11 @@ void authenticated(const QByteArray &id, const QByteArray &token); /** + * This signal is sent if two factor authentication is requested by GitHub. + */ + void twoFactorRequested(); + + /** * This signal is emitted when the model containing repos has * been updated. */ @@ -170,6 +183,7 @@ private: ProviderModel *m_model; QByteArray m_temp, m_orgTemp; + QString m_tfHttpHeader; }; } // End of namespace gh diff --git a/providers/ghprovider/ghresource.cpp b/providers/ghprovider/ghresource.cpp --- a/providers/ghprovider/ghresource.cpp +++ b/providers/ghprovider/ghresource.cpp @@ -35,6 +35,17 @@ /// Base url for the Github API v3. const static QUrl baseUrl("https://api.github.com"); +KIO::StoredTransferJob* getHttpAuthJob(const QString &httpHeader) +{ + QUrl url = baseUrl; + url = url.adjusted(QUrl::StripTrailingSlash); + url.setPath(url.path() + '/' + "/authorizations"); + QByteArray data = "{ \"scopes\": [\"repo\"], \"note\": \"KDevelop Github Provider\" }"; + KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); + job->addMetaData("customHTTPHeader", httpHeader); + return job; +} + Resource::Resource(QObject *parent, ProviderModel *model) : QObject(parent), m_model(model) { @@ -57,12 +68,16 @@ void Resource::authenticate(const QString &name, const QString &password) { - QUrl url = baseUrl; - url = url.adjusted(QUrl::StripTrailingSlash); - url.setPath(url.path() + '/' + "/authorizations"); - QByteArray data = "{ \"scopes\": [\"repo\"], \"note\": \"KDevelop Github Provider\" }"; - KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); - job->addMetaData("customHTTPHeader", "Authorization: Basic " + QString (name + ':' + password).toUtf8().toBase64()); + m_tfHttpHeader.clear(); + auto job = getHttpAuthJob("Authorization: Basic " + QString (name + ':' + password).toUtf8().toBase64()); + job->addMetaData("PropagateHttpHeader","true"); + connect(job, &KIO::StoredTransferJob::result, this, &Resource::slotAuthenticate); + job->start(); +} + +void Resource::twoFactorAuthenticate(const QString &code) { + auto job = getHttpAuthJob(m_tfHttpHeader + "\nX-GitHub-OTP: " + code); + m_tfHttpHeader.clear(); connect(job, &KIO::StoredTransferJob::result, this, &Resource::slotAuthenticate); job->start(); } @@ -137,6 +152,16 @@ return; } + auto metaData = qobject_cast(job)->metaData(); + if (metaData["responsecode"] == QStringLiteral("401")) { + auto& header = metaData["HTTP-Headers"]; + if (header.contains("x-github-otp: required;", Qt::CaseInsensitive)) { + m_tfHttpHeader = qobject_cast(job)->outgoingMetaData()["customHTTPHeader"]; + emit twoFactorRequested(); + return; + } + } + QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(qobject_cast(job)->data(), &error);