From a71864ba9e1cfe49a810d525a17355ab78c80211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Vr=C3=A1til?= Date: Fri, 18 Sep 2020 10:08:27 +0200 Subject: [PATCH] Google: show notification when full authentication is needed When a full authentication is needed (e.g. expired refresh_token) the resource will show a notification instead of just firing up the auth flow. This ensures that the user knows what is going on and the window appears after they explicitly click a button, rather than a random window or a browser popping up out of nowhere with no contextual information of what is going on. --- CMakeLists.txt | 2 +- resources/google-groupware/CMakeLists.txt | 7 +- .../akonadi_google_resource.notifyrc | 11 +++ resources/google-groupware/googleresource.cpp | 80 +++++++++++++++++-- resources/google-groupware/googleresource.h | 4 + .../google-groupware/googlesettingsdialog.cpp | 2 + 6 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 resources/google-groupware/akonadi_google_resource.notifyrc diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e0a0faaf..c70d46cbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,7 @@ set(AKONADIMIME_LIB_VERSION "5.15.40") set(AKONADICONTACT_LIB_VERSION "5.15.40") set(AKONADINOTE_LIB_VERSION "5.15.40") set(PIMCOMMON_LIB_VERSION "5.15.40") -set(KGAPI_LIB_VERSION "5.15.40") +set(KGAPI_LIB_VERSION "5.15.41") set(LIBKDEPIM_LIB_VERSION "5.15.40") set(KLDAP_LIB_VERSION "5.15.40") set(GRANTLEETHEME_LIB_VERSION "5.15.40") diff --git a/resources/google-groupware/CMakeLists.txt b/resources/google-groupware/CMakeLists.txt index 2f189f08c..19fb8ab1d 100644 --- a/resources/google-groupware/CMakeLists.txt +++ b/resources/google-groupware/CMakeLists.txt @@ -73,8 +73,8 @@ target_link_libraries(akonadi_google_resource KF5::Wallet KF5::I18n KF5::WindowSystem - KF5::Completion KF5::TextWidgets + KF5::Notifications KPim::GAPICalendar KPim::GAPIContacts KPim::GAPICore @@ -87,3 +87,8 @@ install( FILES googleresource.desktop DESTINATION "${KDE_INSTALL_DATAROOTDIR}/akonadi/agents" ) + +install( + FILES akonadi_google_resource.notifyrc + DESTINATION ${KNOTIFYRC_INSTALL_DIR} +) diff --git a/resources/google-groupware/akonadi_google_resource.notifyrc b/resources/google-groupware/akonadi_google_resource.notifyrc new file mode 100644 index 000000000..2efa855f2 --- /dev/null +++ b/resources/google-groupware/akonadi_google_resource.notifyrc @@ -0,0 +1,11 @@ +[Global] +IconName=im-google +Name=Google Groupware +Comment=Access your Google Calendars, Contacts and Tasks from KDE + +[Event/authNeeded] +Name=Google Groupware needs to authenticate +Comment=Google Groupware has been logged off and needs to be reauthenticated +Urgency=Normal +Action=Popup + diff --git a/resources/google-groupware/googleresource.cpp b/resources/google-groupware/googleresource.cpp index 425fa0e57..1f7965001 100644 --- a/resources/google-groupware/googleresource.cpp +++ b/resources/google-groupware/googleresource.cpp @@ -1,5 +1,6 @@ /* SPDX-FileCopyrightText: 2011-2013 Daniel Vrátil + SPDX-FileCopyrightText: 2015-2020 Daniel Vrátil SPDX-FileCopyrightText: 2020 Igor Pobiko SPDX-License-Identifier: GPL-3.0-or-later @@ -30,6 +31,7 @@ #include #include #include +#include #include #include @@ -46,6 +48,19 @@ Q_DECLARE_METATYPE(KGAPI2::Job *) using namespace KGAPI2; using namespace Akonadi; +namespace { + +bool accountIsValid(const KGAPI2::AccountPtr &account) +{ + return account + && !account->accessToken().isEmpty() + && !account->refreshToken().isEmpty() + && !account->accountName().isEmpty() + && !account->scopes().isEmpty(); +} + +} // namespace + GoogleResource::GoogleResource(const QString &id) : ResourceBase(id) , AgentBase::ObserverV3() @@ -72,12 +87,19 @@ GoogleResource::GoogleResource(const QString &id) Q_EMIT status(Broken, i18n("Can't access KWallet")); return; } - if (m_settings->accountPtr().isNull()) { + + const auto account = m_settings->accountPtr(); + if (account.isNull()) { Q_EMIT status(NotConfigured); return; } - emitReadyStatus(); - synchronize(); + + if (!accountIsValid(account)) { + requestAuthenticationFromUser(account); + } else { + emitReadyStatus(); + synchronize(); + } }); Q_EMIT status(NotConfigured, i18n("Waiting for KWallet...")); @@ -176,15 +198,19 @@ bool GoogleResource::handleError(KGAPI2::Job *job, bool _cancelTask) AccountPtr account = job->account(); if (job->error() == KGAPI2::Unauthorized) { const QList resourceScopes = scopes(); + bool scopesChanged = false; for (const QUrl &scope : resourceScopes) { if (!account->scopes().contains(scope)) { account->addScope(scope); + scopesChanged = true; } } - AuthJob *authJob = new AuthJob(account, m_settings->clientId(), m_settings->clientSecret(), this); - authJob->setProperty(JOB_PROPERTY, QVariant::fromValue(job)); - connect(authJob, &AuthJob::finished, this, &GoogleResource::slotAuthJobFinished); + if (scopesChanged || !accountIsValid(account)) { + requestAuthenticationFromUser(account, QVariant::fromValue(job)); + } else { + runAuthJob(account, QVariant::fromValue(job)); + } return false; } @@ -194,6 +220,39 @@ bool GoogleResource::handleError(KGAPI2::Job *job, bool _cancelTask) return false; } + +void GoogleResource::runAuthJob(const KGAPI2::AccountPtr &account, const QVariant &args) +{ + AuthJob *authJob = new AuthJob(account, m_settings->clientId(), m_settings->clientSecret(), this); + authJob->setProperty(JOB_PROPERTY, args); + connect(authJob, &AuthJob::finished, this, &GoogleResource::slotAuthJobFinished); +} + +void GoogleResource::requestAuthenticationFromUser(const KGAPI2::AccountPtr &account, const QVariant &args) +{ + Q_EMIT status(Broken, i18n("Account has been logged out.")); + + const QString msg = account->accountName().isEmpty() + ? i18n("Google Groupware has been logged out from your account. Please log in to enable Google Contacts and Calendar sync again.") + : i18n("Google Groupware has been logged out from account %1. Please log in to enable Google Contacts and Calendar sync again.", account->accountName()); + + auto *ntf = KNotification::event(QStringLiteral("authNeeded"), + i18nc("@title", "%1 needs your attention.", agentName()), + msg, + QStringLiteral("im-google"), + /*widget=*/nullptr, + KNotification::Persistent | KNotification::SkipGrouping); + ntf->setActions({i18nc("@action", "Log in")}); + ntf->setComponentName(QStringLiteral("akonadi_google_resource")); + connect(ntf, &KNotification::action1Activated, this, [this, ntf, account, args]() { + runAuthJob(account, args); + ntf->close(); + }); + connect(ntf, &KNotification::ignored, ntf, &KNotification::close); + ntf->sendEvent(); + qCDebug(GOOGLE_LOG) << "Prompting notification" << ntf->id() << " to ask user to reauthenticate"; +} + bool GoogleResource::canPerformTask() { if (!m_settings->accountPtr() && accountId() == 0) { @@ -207,11 +266,18 @@ bool GoogleResource::canPerformTask() void GoogleResource::slotAuthJobFinished(KGAPI2::Job *job) { - if (job->error() != KGAPI2::NoError) { + if (job->error() == KGAPI2::BadRequest) { + auto account = KGAPI2::AccountPtr::create(); + account->setScopes(scopes()); + requestAuthenticationFromUser(account, job->property(JOB_PROPERTY)); + return; + } else if (job->error() != KGAPI2::NoError) { cancelTask(i18n("Failed to refresh tokens")); return; } + Q_EMIT status(Running); + AuthJob *authJob = qobject_cast(job); AccountPtr account = authJob->account(); if (!m_settings->storeAccount(account)) { diff --git a/resources/google-groupware/googleresource.h b/resources/google-groupware/googleresource.h index 2eaa0d215..ff6195c35 100644 --- a/resources/google-groupware/googleresource.h +++ b/resources/google-groupware/googleresource.h @@ -57,6 +57,10 @@ protected: void emitReadyStatus(); void collectionsRetrievedFromHandler(const Akonadi::Collection::List &collections); + + void requestAuthenticationFromUser(const KGAPI2::AccountPtr &account, const QVariant &args = {}); + void runAuthJob(const KGAPI2::AccountPtr &account, const QVariant &args); + protected Q_SLOTS: void retrieveCollections() override; void retrieveItems(const Akonadi::Collection &collection) override; diff --git a/resources/google-groupware/googlesettingsdialog.cpp b/resources/google-groupware/googlesettingsdialog.cpp index 3ecf7843b..0ec4090d1 100644 --- a/resources/google-groupware/googlesettingsdialog.cpp +++ b/resources/google-groupware/googlesettingsdialog.cpp @@ -129,6 +129,7 @@ void GoogleSettingsDialog::accountChanged() void GoogleSettingsDialog::slotConfigure() { + const QString username = m_account && m_account->accountName().isEmpty() ? QString() : m_account->accountName(); m_account = AccountPtr(new Account()); const QList resourceScopes = m_resource->scopes(); for (const QUrl &scope : resourceScopes) { @@ -139,6 +140,7 @@ void GoogleSettingsDialog::slotConfigure() AuthJob *authJob = new AuthJob(m_account, m_settings->clientId(), m_settings->clientSecret()); + authJob->setUsername(username); connect(authJob, &AuthJob::finished, this, &GoogleSettingsDialog::slotAuthJobFinished); } -- GitLab