massive work, enhanced oauth2 post-login lambdas, simplified interfaces for acc editing

This commit is contained in:
Martin Rotter 2021-02-25 14:36:23 +01:00
parent 0a588f7b5e
commit ee5de37805
16 changed files with 86 additions and 84 deletions

View File

@ -47,7 +47,8 @@ OAuth2Service::OAuth2Service(const QString& auth_url, const QString& token_url,
const QString& client_secret, const QString& scope, QObject* parent) const QString& client_secret, const QString& scope, QObject* parent)
: QObject(parent), : QObject(parent),
m_id(QString::number(QRandomGenerator::global()->generate())), m_timerId(-1), m_id(QString::number(QRandomGenerator::global()->generate())), m_timerId(-1),
m_redirectionHandler(new OAuthHttpHandler(tr("You can close this window now. Go back to %1.").arg(APP_NAME), this)) { m_redirectionHandler(new OAuthHttpHandler(tr("You can close this window now. Go back to %1.").arg(APP_NAME), this)),
m_functorOnLogin({}) {
m_tokenGrantType = QSL("authorization_code"); m_tokenGrantType = QSL("authorization_code");
m_tokenUrl = QUrl(token_url); m_tokenUrl = QUrl(token_url);
m_authUrl = auth_url; m_authUrl = auth_url;
@ -229,6 +230,7 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
<< "Obtained refresh token" << QUOTE_W_SPACE(refreshToken()) << "Obtained refresh token" << QUOTE_W_SPACE(refreshToken())
<< "- expires on date/time" << QUOTE_W_SPACE_DOT(tokensExpireIn()); << "- expires on date/time" << QUOTE_W_SPACE_DOT(tokensExpireIn());
m_functorOnLogin();
emit tokensRetrieved(accessToken(), refreshToken(), expires); emit tokensRetrieved(accessToken(), refreshToken(), expires);
} }
@ -297,7 +299,9 @@ void OAuth2Service::setRefreshToken(const QString& refresh_token) {
startRefreshTimer(); startRefreshTimer();
} }
bool OAuth2Service::login() { bool OAuth2Service::login(const std::function<void()>& functor_when_logged_in) {
m_functorOnLogin = {};
if (!m_redirectionHandler->isListening()) { if (!m_redirectionHandler->isListening()) {
qCriticalNN << LOGSEC_OAUTH qCriticalNN << LOGSEC_OAUTH
<< "Cannot log-in because OAuth redirection handler is not listening."; << "Cannot log-in because OAuth redirection handler is not listening.";
@ -312,6 +316,8 @@ bool OAuth2Service::login() {
bool did_token_expire = tokensExpireIn().isNull() || tokensExpireIn() < QDateTime::currentDateTime().addSecs(-120); bool did_token_expire = tokensExpireIn().isNull() || tokensExpireIn() < QDateTime::currentDateTime().addSecs(-120);
bool does_token_exist = !refreshToken().isEmpty(); bool does_token_exist = !refreshToken().isEmpty();
m_functorOnLogin = functor_when_logged_in;
// We refresh current tokens only if: // We refresh current tokens only if:
// 1. We have some existing refresh token. // 1. We have some existing refresh token.
// AND // AND
@ -325,6 +331,7 @@ bool OAuth2Service::login() {
return false; return false;
} }
else { else {
functor_when_logged_in();
return true; return true;
} }
} }

View File

@ -26,11 +26,14 @@
#define OAUTH2SERVICE_H #define OAUTH2SERVICE_H
#include <QObject> #include <QObject>
#include <QUrl>
#include "network-web/oauthhttphandler.h" #include "network-web/oauthhttphandler.h"
#include "network-web/silentnetworkaccessmanager.h" #include "network-web/silentnetworkaccessmanager.h"
#include <functional>
#include <QUrl>
class OAuth2Service : public QObject { class OAuth2Service : public QObject {
Q_OBJECT Q_OBJECT
@ -96,10 +99,7 @@ class OAuth2Service : public QObject {
// //
// Returns true, if user is already logged in (final state). // Returns true, if user is already logged in (final state).
// Returns false, if user is NOT logged in (asynchronous flow). // Returns false, if user is NOT logged in (asynchronous flow).
// bool login(const std::function<void()>& functor_when_logged_in = {});
// NOTE: This can be called ONLY on main GUI thread,
// because widgets may be displayed.
bool login();
// Removes all state data and stops redirection handler. // Removes all state data and stops redirection handler.
void logout(bool stop_redirection_handler = true); void logout(bool stop_redirection_handler = true);
@ -131,6 +131,7 @@ class OAuth2Service : public QObject {
QString m_scope; QString m_scope;
SilentNetworkAccessManager m_networkManager; SilentNetworkAccessManager m_networkManager;
OAuthHttpHandler* m_redirectionHandler; OAuthHttpHandler* m_redirectionHandler;
std::function<void()> m_functorOnLogin;
}; };
#endif // OAUTH2SERVICE_H #endif // OAUTH2SERVICE_H

View File

@ -390,6 +390,7 @@ void ServiceRoot::syncIn() {
setIcon(original_icon); setIcon(original_icon);
itemChanged(getSubTree()); itemChanged(getSubTree());
requestItemExpand({ this }, true);
} }
void ServiceRoot::performInitialAssembly(const Assignment& categories, const Assignment& feeds, const QList<Label*>& labels) { void ServiceRoot::performInitialAssembly(const Assignment& categories, const Assignment& feeds, const QList<Label*>& labels) {

View File

@ -22,12 +22,6 @@ FormEditGmailAccount::FormEditGmailAccount(QWidget* parent)
void FormEditGmailAccount::apply() { void FormEditGmailAccount::apply() {
FormAccountDetails::apply(); FormAccountDetails::apply();
if (!m_creatingNew) {
// Disable "Cancel" button because all changes made to
// existing account are always saved anyway.
m_ui.m_buttonBox->button(QDialogButtonBox::StandardButton::Cancel)->setVisible(false);
}
// Make sure that the data copied from GUI are used for brand new login. // Make sure that the data copied from GUI are used for brand new login.
account<GmailServiceRoot>()->network()->oauth()->logout(false); account<GmailServiceRoot>()->network()->oauth()->logout(false);
account<GmailServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text()); account<GmailServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
@ -42,9 +36,6 @@ void FormEditGmailAccount::apply() {
if (!m_creatingNew) { if (!m_creatingNew) {
account<GmailServiceRoot>()->completelyRemoveAllData(); account<GmailServiceRoot>()->completelyRemoveAllData();
// Account data are erased, it is similar to situation
// where we start the account after it was freshly added.
account<GmailServiceRoot>()->start(true); account<GmailServiceRoot>()->start(true);
} }
} }
@ -52,6 +43,12 @@ void FormEditGmailAccount::apply() {
void FormEditGmailAccount::loadAccountData() { void FormEditGmailAccount::loadAccountData() {
FormAccountDetails::loadAccountData(); FormAccountDetails::loadAccountData();
if (!m_creatingNew) {
// Disable "Cancel" button because all changes made to
// existing account are always saved anyway.
m_ui.m_buttonBox->button(QDialogButtonBox::StandardButton::Cancel)->setVisible(false);
}
m_details->m_oauth = account<GmailServiceRoot>()->network()->oauth(); m_details->m_oauth = account<GmailServiceRoot>()->network()->oauth();
m_details->hookNetwork(); m_details->hookNetwork();

View File

@ -58,21 +58,8 @@ GmailAccountDetails::GmailAccountDetails(QWidget* parent)
void GmailAccountDetails::testSetup() { void GmailAccountDetails::testSetup() {
m_oauth->logout(); m_oauth->logout();
#if defined(GMAIL_OFFICIAL_SUPPORT)
if (m_ui.m_txtAppId->lineEdit()->text().isEmpty() || m_ui.m_txtAppKey->lineEdit()->text().isEmpty()) {
m_oauth->setClientId(TextFactory::decrypt(GMAIL_CLIENT_ID, OAUTH_DECRYPTION_KEY));
m_oauth->setClientSecret(TextFactory::decrypt(GMAIL_CLIENT_SECRET, OAUTH_DECRYPTION_KEY));
}
else {
#endif
m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text()); m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text());
m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text()); m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text());
#if defined(GMAIL_OFFICIAL_SUPPORT)
}
#endif
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text()); m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text());
if (m_oauth->login()) { if (m_oauth->login()) {

View File

@ -51,11 +51,13 @@ bool GreaderServiceRoot::deleteViaGui() {
} }
void GreaderServiceRoot::start(bool freshly_activated) { void GreaderServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated) if (!freshly_activated) {
loadFromDatabase(); loadFromDatabase();
loadCacheFromFile(); loadCacheFromFile();
if (childCount() <= 3) { }
if (getSubTreeFeeds().isEmpty()) {
syncIn(); syncIn();
} }
} }

View File

@ -34,7 +34,7 @@ void FormEditGreaderAccount::apply() {
if (!m_creatingNew) { if (!m_creatingNew) {
account<GreaderServiceRoot>()->completelyRemoveAllData(); account<GreaderServiceRoot>()->completelyRemoveAllData();
account<GreaderServiceRoot>()->syncIn(); account<GreaderServiceRoot>()->start(true);
} }
} }

View File

@ -8,9 +8,6 @@
#define INOREADER_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth" #define INOREADER_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth"
#define INOREADER_REG_API_URL "https://www.inoreader.com/developers/register-app" #define INOREADER_REG_API_URL "https://www.inoreader.com/developers/register-app"
#define INOREADER_OAUTH_CLI_ID "999999019"
#define INOREADER_OAUTH_CLI_KEY "k4bkOJ5Jj1erc52tmniluKtU6lZdNoZc"
#define INOREADER_REFRESH_TOKEN_KEY "refresh_token" #define INOREADER_REFRESH_TOKEN_KEY "refresh_token"
#define INOREADER_ACCESS_TOKEN_KEY "access_token" #define INOREADER_ACCESS_TOKEN_KEY "access_token"

View File

@ -25,16 +25,13 @@ FormEditInoreaderAccount::FormEditInoreaderAccount(QWidget* parent)
void FormEditInoreaderAccount::apply() { void FormEditInoreaderAccount::apply() {
FormAccountDetails::apply(); FormAccountDetails::apply();
if (m_creatingNew) { if (!m_creatingNew) {
// We transfer refresh token to avoid the need to login once more, // Disable "Cancel" button because all changes made to
// then we delete testing OAuth service. // existing account are always saved anyway.
account<InoreaderServiceRoot>()->network()->oauth()->setRefreshToken(m_details->m_oauth->refreshToken()); m_ui.m_buttonBox->button(QDialogButtonBox::StandardButton::Cancel)->setVisible(false);
account<InoreaderServiceRoot>()->network()->oauth()->setAccessToken(m_details->m_oauth->accessToken());
account<InoreaderServiceRoot>()->network()->oauth()->setTokensExpireIn(m_details->m_oauth->tokensExpireIn());
m_details->m_oauth->logout(true);
m_details->m_oauth->deleteLater();
} }
account<InoreaderServiceRoot>()->network()->oauth()->logout(false);
account<InoreaderServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text()); account<InoreaderServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text()); account<InoreaderServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
account<InoreaderServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text()); account<InoreaderServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text());
@ -47,18 +44,17 @@ void FormEditInoreaderAccount::apply() {
if (!m_creatingNew) { if (!m_creatingNew) {
account<InoreaderServiceRoot>()->completelyRemoveAllData(); account<InoreaderServiceRoot>()->completelyRemoveAllData();
account<InoreaderServiceRoot>()->syncIn(); account<InoreaderServiceRoot>()->start(true);
} }
} }
void FormEditInoreaderAccount::loadAccountData() { void FormEditInoreaderAccount::loadAccountData() {
FormAccountDetails::loadAccountData(); FormAccountDetails::loadAccountData();
if (m_details->m_oauth != nullptr) { if (!m_creatingNew) {
// We will use live OAuth service for testing. // Disable "Cancel" button because all changes made to
m_details->m_oauth->logout(true); // existing account are always saved anyway.
delete m_details->m_oauth; m_ui.m_buttonBox->button(QDialogButtonBox::StandardButton::Cancel)->setVisible(false);
m_details->m_oauth = nullptr;
} }
m_details->m_oauth = account<InoreaderServiceRoot>()->network()->oauth(); m_details->m_oauth = account<InoreaderServiceRoot>()->network()->oauth();

View File

@ -10,17 +10,20 @@
#include "services/inoreader/network/inoreadernetworkfactory.h" #include "services/inoreader/network/inoreadernetworkfactory.h"
InoreaderAccountDetails::InoreaderAccountDetails(QWidget* parent) InoreaderAccountDetails::InoreaderAccountDetails(QWidget* parent)
: QWidget(parent), m_oauth(new OAuth2Service(INOREADER_OAUTH_AUTH_URL, INOREADER_OAUTH_TOKEN_URL, : QWidget(parent), m_oauth(nullptr) {
{}, {}, INOREADER_OAUTH_SCOPE, this)) {
m_ui.setupUi(this); m_ui.setupUi(this);
GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true); GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true);
m_ui.m_lblInfo->setText(tr("Specified redirect URL must start with \"http://localhost\" and " #if defined(INOREADER_OFFICIAL_SUPPORT)
"must be configured in your OAuth \"application\".\n\n" m_ui.m_lblInfo->setText(tr("There are some preconfigured OAuth tokens so you do not have to fill in your "
"It is highly recommended to create your own \"App ID\". " "client ID/secret, but it is strongly recommended to obtain your "
"Because predefined one may be limited due to usage quotas if used by " "own as it preconfigured tokens have limited global usage quota. If you wash "
"too many users simultaneously.")); "to use preconfigured tokens, simply leave those fields empty and make sure "
"to leave default value of redirect URL."));
#else
m_ui.m_lblInfo->setText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."));
#endif
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information, m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
tr("Not tested yet."), tr("Not tested yet."),
@ -46,12 +49,9 @@ InoreaderAccountDetails::InoreaderAccountDetails(QWidget* parent)
m_ui.m_spinLimitMessages->setMaximum(INOREADER_MAX_BATCH_SIZE); m_ui.m_spinLimitMessages->setMaximum(INOREADER_MAX_BATCH_SIZE);
emit m_ui.m_txtUsername->lineEdit()->textChanged(m_ui.m_txtUsername->lineEdit()->text()); emit m_ui.m_txtUsername->lineEdit()->textChanged(m_ui.m_txtUsername->lineEdit()->text());
emit m_ui.m_txtAppId->lineEdit()->textChanged(m_ui.m_txtAppId->lineEdit()->text());
m_ui.m_txtAppId->lineEdit()->setText(INOREADER_OAUTH_CLI_ID); emit m_ui.m_txtAppKey->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
m_ui.m_txtAppKey->lineEdit()->setText(INOREADER_OAUTH_CLI_KEY); emit m_ui.m_txtRedirectUrl->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
m_ui.m_txtRedirectUrl->lineEdit()->setText(QString(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(OAUTH_REDIRECT_URI_PORT));
hookNetwork(); hookNetwork();
} }
@ -113,7 +113,11 @@ void InoreaderAccountDetails::checkOAuthValue(const QString& value) {
if (line_edit != nullptr) { if (line_edit != nullptr) {
if (value.isEmpty()) { if (value.isEmpty()) {
#if defined(INOREADER_OFFICIAL_SUPPORT)
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Preconfigured client ID/secret will be used."));
#else
line_edit->setStatus(WidgetWithStatus::StatusType::Error, tr("Empty value is entered.")); line_edit->setStatus(WidgetWithStatus::StatusType::Error, tr("Empty value is entered."));
#endif
} }
else { else {
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some value is entered.")); line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some value is entered."));

View File

@ -94,16 +94,15 @@ bool InoreaderServiceRoot::supportsCategoryAdding() const {
} }
void InoreaderServiceRoot::start(bool freshly_activated) { void InoreaderServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated) if (!freshly_activated) {
loadFromDatabase(); loadFromDatabase();
loadCacheFromFile(); loadCacheFromFile();
if (childCount() <= 3) {
syncIn();
} }
else {
m_network->oauth()->login(); if (getSubTreeFeeds().isEmpty()) {
m_network->oauth()->login([this]() {
syncIn();
});
} }
} }

View File

@ -53,6 +53,15 @@ void InoreaderNetworkFactory::setBatchSize(int batch_size) {
} }
void InoreaderNetworkFactory::initializeOauth() { void InoreaderNetworkFactory::initializeOauth() {
#if defined(INOREADER_OFFICIAL_SUPPORT)
m_oauth2->setClientSecretId(TextFactory::decrypt(INOREADER_CLIENT_ID, OAUTH_DECRYPTION_KEY));
m_oauth2->setClientSecretSecret(TextFactory::decrypt(INOREADER_CLIENT_SECRET, OAUTH_DECRYPTION_KEY));
#endif
m_oauth2->setRedirectUrl(QString(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(OAUTH_REDIRECT_URI_PORT));
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &InoreaderNetworkFactory::onTokensError); connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &InoreaderNetworkFactory::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &InoreaderNetworkFactory::onAuthFailed); connect(m_oauth2, &OAuth2Service::authFailed, this, &InoreaderNetworkFactory::onAuthFailed);
connect(m_oauth2, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, QString refresh_token, int expires_in) { connect(m_oauth2, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, QString refresh_token, int expires_in) {

View File

@ -35,7 +35,7 @@ void FormEditOwnCloudAccount::apply() {
if (!m_creatingNew) { if (!m_creatingNew) {
account<OwnCloudServiceRoot>()->completelyRemoveAllData(); account<OwnCloudServiceRoot>()->completelyRemoveAllData();
account<OwnCloudServiceRoot>()->syncIn(); account<OwnCloudServiceRoot>()->start(true);
} }
} }

View File

@ -63,11 +63,12 @@ bool OwnCloudServiceRoot::supportsCategoryAdding() const {
} }
void OwnCloudServiceRoot::start(bool freshly_activated) { void OwnCloudServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated) if (!freshly_activated) {
loadFromDatabase(); loadFromDatabase();
loadCacheFromFile(); loadCacheFromFile();
}
if (childCount() <= 3) { if (getSubTreeFeeds().isEmpty()) {
syncIn(); syncIn();
} }
} }

View File

@ -20,6 +20,7 @@ FormEditTtRssAccount::FormEditTtRssAccount(QWidget* parent)
void FormEditTtRssAccount::apply() { void FormEditTtRssAccount::apply() {
FormAccountDetails::apply(); FormAccountDetails::apply();
account<TtRssServiceRoot>()->network()->logout(m_account->networkProxy());
account<TtRssServiceRoot>()->network()->setUrl(m_details->m_ui.m_txtUrl->lineEdit()->text()); account<TtRssServiceRoot>()->network()->setUrl(m_details->m_ui.m_txtUrl->lineEdit()->text());
account<TtRssServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text()); account<TtRssServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<TtRssServiceRoot>()->network()->setPassword(m_details->m_ui.m_txtPassword->lineEdit()->text()); account<TtRssServiceRoot>()->network()->setPassword(m_details->m_ui.m_txtPassword->lineEdit()->text());
@ -33,9 +34,8 @@ void FormEditTtRssAccount::apply() {
accept(); accept();
if (!m_creatingNew) { if (!m_creatingNew) {
account<TtRssServiceRoot>()->network()->logout(m_account->networkProxy());
account<TtRssServiceRoot>()->completelyRemoveAllData(); account<TtRssServiceRoot>()->completelyRemoveAllData();
account<TtRssServiceRoot>()->syncIn(); account<TtRssServiceRoot>()->start(true);
} }
} }

View File

@ -37,11 +37,12 @@ ServiceRoot::LabelOperation TtRssServiceRoot::supportedLabelOperations() const {
} }
void TtRssServiceRoot::start(bool freshly_activated) { void TtRssServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated) if (!freshly_activated) {
loadFromDatabase(); loadFromDatabase();
loadCacheFromFile(); loadCacheFromFile();
}
if (childCount() <= 3) { if (getSubTreeFeeds().isEmpty()) {
syncIn(); syncIn();
} }
} }