From af06a680c8a553ab2f539408fbe9469a417e9ba7 Mon Sep 17 00:00:00 2001 From: Framawiki Date: Sat, 11 Nov 2017 20:27:35 +0100 Subject: [PATCH 1/2] First commit for clientlogin + 2FA To be able to login with 2FA enabled, we need to use clientlogin instead of login. In case of 2fa, a second http request is needed to send user, pass, totp (2fa code), fake loginreturnurl, rememberMe boolean and our classical token. It's the first commit, currently the login form works as usual for normal users. users with 2fa enabled want be connected yet. **So this commit is not ready to be merged for now.** Inspired by https://github.com/commons-app/apps-android-commons/blob/b0e8175003a686789474238dd293aa89d1e925c7/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java#L93 Bug: https://phabricator.wikimedia.org/T180279 --- huggle/Localization/en.xml | 1 + huggle/apiquery.cpp | 5 ++ huggle/apiquery.hpp | 1 + huggle/login.cpp | 95 +++++++++++++++++++++++++------------- 4 files changed, 69 insertions(+), 33 deletions(-) diff --git a/huggle/Localization/en.xml b/huggle/Localization/en.xml index 8787f602b..8a2555a81 100644 --- a/huggle/Localization/en.xml +++ b/huggle/Localization/en.xml @@ -298,6 +298,7 @@ Switch to read-only One or more projects ($1) do not allow you to login with edit permissions (reason: $2). Do you want to switch to read-only mode instead? Project $1 switched to read-only mode + This functionality is not yet implemented. Please contact developers at huggle@lists.wikimedia.org. $1 edits per minute, $2 reverts per minute, level $3 Stop provider Resume provider diff --git a/huggle/apiquery.cpp b/huggle/apiquery.cpp index d3ceaf6be..8b38935c8 100644 --- a/huggle/apiquery.cpp +++ b/huggle/apiquery.cpp @@ -407,6 +407,7 @@ void ApiQuery::SetAction(const Action action) this->ActionPart = "clearhasmsg"; this->UsingPOST = true; return; + ///! \todo ActionQuery still used ? case ActionQuery: this->ActionPart = "query"; this->IsContinuous = true; @@ -416,6 +417,10 @@ void ApiQuery::SetAction(const Action action) this->ActionPart = "login"; this->EnforceLogin = false; return; + case ClientLogin: + this->ActionPart = "clientlogin"; + this->EnforceLogin = false; + return; case ActionLogout: this->ActionPart = "logout"; return; diff --git a/huggle/apiquery.hpp b/huggle/apiquery.hpp index 576cc2e2c..e7e16ed69 100644 --- a/huggle/apiquery.hpp +++ b/huggle/apiquery.hpp @@ -31,6 +31,7 @@ namespace Huggle ActionClearHasMsg, ActionQuery, ActionLogin, + ClientLogin, ActionLogout, //ActionTokens, ActionPurge, diff --git a/huggle/login.cpp b/huggle/login.cpp index 834c2f65e..a760344ce 100644 --- a/huggle/login.cpp +++ b/huggle/login.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #define LOGINFORM_LOGIN 0 #define LOGINFORM_SITEINFO 1 @@ -554,21 +555,20 @@ void Login::PerformLoginPart2(WikiSite *site) this->Statuses[site] = WaitingForToken; this->LoginQueries.remove(site); query->DecRef(); - query = new ApiQuery(ActionLogin, site); + query = new ApiQuery(ClientLogin, site); this->LoginQueries.insert(site, query); - query->HiddenQuery = true; + //query->HiddenQuery = true; query->IncRef(); if (hcfg->SystemConfig_BotPassword) { - query->Parameters = "lgname=" + QUrl::toPercentEncoding(hcfg->SystemConfig_BotLogin) - + "&lgpassword=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password) - + "&lgtoken=" + QUrl::toPercentEncoding(token); + query->Parameters = "username=" + QUrl::toPercentEncoding(hcfg->SystemConfig_BotLogin) + + "&password=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password); } else { - query->Parameters = "lgname=" + QUrl::toPercentEncoding(hcfg->SystemConfig_Username) - + "&lgpassword=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password) - + "&lgtoken=" + QUrl::toPercentEncoding(token); + query->Parameters = "username=" + QUrl::toPercentEncoding(hcfg->SystemConfig_Username) + + "&password=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password); } + query->Parameters = query->Parameters + "&loginreturnurl=http://example.com/&rememberMe=1&logintoken=" + QUrl::toPercentEncoding(token); query->UsingPOST = true; query->Process(); } @@ -1269,42 +1269,71 @@ bool Login::ProcessOutput(WikiSite *site) ApiQuery *query = this->LoginQueries[site]; // Check what the result was ApiQueryResult *result = query->GetApiQueryResult(); - ApiQueryResultNode *ln = result->GetNode("login"); - QString result_code = ln->GetAttribute("result"); - QString reason = ln->GetAttribute("reason"); - if (result_code.isEmpty()) + ApiQueryResultNode *ln = result->GetNode("clientlogin"); + QString status = ln->GetAttribute("status"); + if (status.isEmpty()) { this->DisplayError(_l("api.php-invalid-response")); return false; } - if (result_code == "Success") + + if (status == "PASS") return true; - if (result_code == "EmptyPass") - { - this->DisplayError(_l("login-password-empty")); + if (status == "UI") { + // Need a user interaction like captacha or 2FA + //QString v_id = ln->ChildNodes.at(0)->GetAttribute("id", "unknown"); + //if (v_id == "TOTPAuthenticationRequest"){ + if (true){ + // 2FA is requierd (TOTP code needed) + QString totp = QInputDialog::getText(this, "Two factor authentification", "Please enter the 2FA code from your device:"); + query = new ApiQuery(ClientLogin, site); + //query->HiddenQuery = true; + query->IncRef(); + query->Parameters = "username=" + QUrl::toPercentEncoding(hcfg->SystemConfig_BotLogin) + + "&password=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password) + + "&OATHToken=" + totp + "&loginreturnurl=http://example.com/&rememberMe=1&logintoken=" + QUrl::toPercentEncoding(this->Tokens[site]); + query->UsingPOST = true; + query->Process(); + ApiQueryResult *result = query->GetApiQueryResult(); + ApiQueryResultNode *ln = result->GetNode("clientlogin"); + } return false; } - if (result_code == "WrongPass") - { - /// \bug This sometimes doesn't work properly - this->ui->lineEdit_password->setFocus(); - this->DisplayError(_l("login-error-password")); + if (status == "REDIRECT") + // Need to login using another web service + this->DisplayError(_l("not-implemented")); return false; - } - if (result_code == "NoName") + if (status == "FAIL") { - this->DisplayError(_l("login-fail-wrong-name")); - return false; - } - if (result_code == "NotExists") - { - this->DisplayError(_l("login-username-doesnt-exist")); + QString message = ln->GetAttribute("message"); + QString message_code = ln->GetAttribute("messagecode"); + if (message_code == "wrongpassword") { + /// \bug This sometimes doesn't work properly + this->ui->lineEdit_password->setFocus(); + this->DisplayError(_l("login-error-password")); + return false; + } + /// \todo Verify these error codes + if (message_code == "EmptyPass") + { + this->DisplayError(_l("login-password-empty")); + return false; + } + if (message_code == "NoName") + { + this->DisplayError(_l("login-fail-wrong-name")); + return false; + } + if (message_code == "NotExists") + { + this->DisplayError(_l("login-username-doesnt-exist")); + return false; + } + if (message.isEmpty()) + message = message_code; + this->DisplayError(_l("login-api", message)); return false; } - if (reason.isEmpty()) - reason = result_code; - this->DisplayError(_l("login-api", reason)); - return false; } void Login::on_ButtonOK_clicked() From 3e684810cef9498af1eaeb1365c422b0b0035063 Mon Sep 17 00:00:00 2001 From: Framawiki Date: Sun, 12 Nov 2017 19:06:40 +0100 Subject: [PATCH 2/2] improvements --- huggle/apiquery.cpp | 2 +- huggle/apiquery.hpp | 2 +- huggle/login.cpp | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/huggle/apiquery.cpp b/huggle/apiquery.cpp index 8b38935c8..fc0886e30 100644 --- a/huggle/apiquery.cpp +++ b/huggle/apiquery.cpp @@ -417,7 +417,7 @@ void ApiQuery::SetAction(const Action action) this->ActionPart = "login"; this->EnforceLogin = false; return; - case ClientLogin: + case ActionClientLogin: this->ActionPart = "clientlogin"; this->EnforceLogin = false; return; diff --git a/huggle/apiquery.hpp b/huggle/apiquery.hpp index e7e16ed69..7b59f0ab6 100644 --- a/huggle/apiquery.hpp +++ b/huggle/apiquery.hpp @@ -31,7 +31,7 @@ namespace Huggle ActionClearHasMsg, ActionQuery, ActionLogin, - ClientLogin, + ActionClientLogin, ActionLogout, //ActionTokens, ActionPurge, diff --git a/huggle/login.cpp b/huggle/login.cpp index a760344ce..7683889c2 100644 --- a/huggle/login.cpp +++ b/huggle/login.cpp @@ -555,7 +555,7 @@ void Login::PerformLoginPart2(WikiSite *site) this->Statuses[site] = WaitingForToken; this->LoginQueries.remove(site); query->DecRef(); - query = new ApiQuery(ClientLogin, site); + query = new ApiQuery(ActionClientLogin, site); this->LoginQueries.insert(site, query); //query->HiddenQuery = true; query->IncRef(); @@ -1286,12 +1286,14 @@ bool Login::ProcessOutput(WikiSite *site) if (true){ // 2FA is requierd (TOTP code needed) QString totp = QInputDialog::getText(this, "Two factor authentification", "Please enter the 2FA code from your device:"); - query = new ApiQuery(ClientLogin, site); + query = new ApiQuery(ActionClientLogin, site); //query->HiddenQuery = true; query->IncRef(); query->Parameters = "username=" + QUrl::toPercentEncoding(hcfg->SystemConfig_BotLogin) + "&password=" + QUrl::toPercentEncoding(hcfg->TemporaryConfig_Password) - + "&OATHToken=" + totp + "&loginreturnurl=http://example.com/&rememberMe=1&logintoken=" + QUrl::toPercentEncoding(this->Tokens[site]); + + "&OATHToken=" + totp + + + "&logintoken=" + QUrl::toPercentEncoding(this->Tokens[site]) + + "&logincontinue=1&rememberMe=1"; query->UsingPOST = true; query->Process(); ApiQueryResult *result = query->GetApiQueryResult();