1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
/*
SPDX-FileCopyrightText: 2023 Daniel Vrátil <dvratil@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "outlookpasswordrequester.h"
#include "imapresource_debug.h"
#include "imapresourcebase.h"
#include "settings.h"
#include <MailTransport/OutlookOAuthTokenRequester>
#include <KLocalizedString>
#include <qt6keychain/keychain.h>
#include <memory>
using namespace QKeychain;
static constexpr QLatin1StringView clientId{"18da2bc3-146a-4581-8c92-27dc7b9954a0"};
static constexpr QLatin1StringView tenantId{"common"};
static const QStringList scopes{
QLatin1StringView("https://outlook.office.com/IMAP.AccessAsUser.All"),
QLatin1StringView("offline_access"),
};
static constexpr QLatin1StringView walletFolder = QLatin1StringView("imap");
OutlookPasswordRequester::OutlookPasswordRequester(ImapResourceBase *resource, QObject *parent)
: XOAuthPasswordRequester(parent)
, mResource(resource)
{
}
OutlookPasswordRequester::~OutlookPasswordRequester() = default;
void OutlookPasswordRequester::requestPassword(RequestType request, const QString &serverError)
{
Q_UNUSED(serverError) // we don't get anything useful from XOAUTH2 SASL
if (mRequestInProgress) {
qCDebug(IMAPRESOURCE_LOG) << "Outlook OAuth2 token request already in progress";
return;
}
mTokenRequester = std::make_unique<MailTransport::OutlookOAuthTokenRequester>(clientId, tenantId, scopes);
connect(mTokenRequester.get(), &MailTransport::OutlookOAuthTokenRequester::finished, this, [this](const auto &result) {
onTokenRequestFinished(result);
});
auto readJob = new ReadPasswordJob(walletFolder, this);
readJob->setKey(mResource->settings()->config()->name());
connect(readJob, &ReadPasswordJob::finished, this, [this, readJob, request]() {
if (readJob->error() == QKeychain::Error::EntryNotFound) {
qCDebug(IMAPRESOURCE_LOG) << "No Outlook OAuth2 tokens found in KWallet, requesting new token...";
mTokenRequester->requestToken(mResource->settings()->userName());
return;
} else if (readJob->error() != QKeychain::Error::NoError) {
mRequestInProgress = false;
qCWarning(IMAPRESOURCE_LOG) << "Failed to read password from keychain.";
Q_EMIT done(UserRejected, i18nc("@status", "Failed to read password from keychain."));
return;
}
QMap<QString, QString> map;
auto value = readJob->binaryData();
if (value.isEmpty()) {
mRequestInProgress = false;
qCWarning(IMAPRESOURCE_LOG) << "Failed to read password from keychain.";
Q_EMIT done(UserRejected, i18nc("@status", "Failed to read password from keychain."));
return;
}
QDataStream ds(value);
ds >> map;
if (request == WrongPasswordRequest) {
const auto refreshToken = map[QStringLiteral("refreshToken")];
if (!refreshToken.isEmpty()) {
qCDebug(IMAPRESOURCE_LOG) << "Found an Outlook OAuth2 refresh token in KWallet, refreshing access token...";
mTokenRequester->refreshToken(refreshToken);
} else {
qCDebug(IMAPRESOURCE_LOG) << "No Outlook OAuth2 refresh token found in KWallet, requesting new token...";
mTokenRequester->requestToken(mResource->settings()->userName());
}
} else {
const auto accessToken = map[QStringLiteral("accessToken")];
if (accessToken.isEmpty()) {
qCDebug(IMAPRESOURCE_LOG) << "No Outlook OAuth2 access token found in KWallet, requesting new token...";
mTokenRequester->requestToken(mResource->settings()->userName());
} else {
qCDebug(IMAPRESOURCE_LOG) << "Found an Outlook OAuth2 access token in KWallet, using it...";
mRequestInProgress = false;
Q_EMIT done(PasswordRetrieved, accessToken);
}
}
});
readJob->start();
}
void OutlookPasswordRequester::cancelPasswordRequests()
{
if (mTokenRequester) {
mTokenRequester->disconnect(this);
mTokenRequester.release()->deleteLater();
qCDebug(IMAPRESOURCE_LOG) << "Canceled Outlook OAuth2 token request";
}
}
void OutlookPasswordRequester::storeResultToWallet(const MailTransport::TokenResult &result)
{
const auto name = mResource->settings()->config()->name();
QByteArray mapData;
QDataStream ds(&mapData, QIODeviceBase::WriteOnly);
ds << QMap<QString, QString>{{QStringLiteral("accessToken"), result.accessToken()}, {QStringLiteral("refreshToken"), result.refreshToken()}};
auto writeJob = new WritePasswordJob(walletFolder, this);
writeJob->setKey(mResource->settings()->config()->name());
writeJob->setBinaryData(mapData);
connect(writeJob, &WritePasswordJob::finished, this, [this, writeJob, name]() {
if (writeJob->error() != QKeychain::Error::NoError) {
qCWarning(IMAPRESOURCE_LOG) << "Failed to store Outlook OAuth2 token to KWallet.";
return;
}
qCDebug(IMAPRESOURCE_LOG).nospace().noquote() << "Storing Outlook OAuth2 token to KWallet (" << walletFolder << "/" << name << ")";
});
writeJob->start();
}
void OutlookPasswordRequester::onTokenRequestFinished(const MailTransport::TokenResult &result)
{
mRequestInProgress = false;
mTokenRequester.release()->deleteLater();
if (result.hasError()) {
qCDebug(IMAPRESOURCE_LOG) << "Outlook OAuth2 token request failed:" << result.errorText();
Q_EMIT done(UserRejected, result.errorText());
} else {
storeResultToWallet(result);
Q_EMIT done(PasswordRetrieved, result.accessToken());
}
}
#include "moc_outlookpasswordrequester.cpp"
|