Verified Commit a71864ba authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

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.
parent b9a46ef3
Pipeline #36370 failed with stage
in 60 minutes and 26 seconds
......@@ -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")
......
......@@ -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}
)
[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
/*
SPDX-FileCopyrightText: 2011-2013 Daniel Vrátil <dvratil@redhat.com>
SPDX-FileCopyrightText: 2015-2020 Daniel Vrátil <dvratil@kde.org>
SPDX-FileCopyrightText: 2020 Igor Pobiko <igor.poboiko@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
......@@ -30,6 +31,7 @@
#include <QDialog>
#include <QIcon>
#include <KLocalizedString>
#include <KNotification>
#include <KGAPI/Account>
#include <KGAPI/AccountInfoFetchJob>
......@@ -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<QUrl> 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<AuthJob *>(job);
AccountPtr account = authJob->account();
if (!m_settings->storeAccount(account)) {
......
......@@ -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;
......
......@@ -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<QUrl> 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);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment