MailTransport

outlookpasswordrequester.cpp
1/*
2 SPDX-FileCopyrightText: 2024 g10 Code GmbH
3 SPDX-FileContributor: Daniel Vrátil <dvratil@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "outlookpasswordrequester.h"
9#include "mailtransportplugin_smtp_debug.h"
10#include "outlookoauthtokenrequester.h"
11#include "transport.h"
12
13#include <QDataStream>
14#include <QVariantMap>
15
16#include <memory>
17#include <qt6keychain/keychain.h>
18
19using namespace MailTransport;
20
21static const QString clientId = QStringLiteral("18da2bc3-146a-4581-8c92-27dc7b9954a0");
22static const QString tenantId = QStringLiteral("common");
23static const QStringList scopes = {QStringLiteral("https://outlook.office.com/SMTP.Send"), QStringLiteral("offline_access")};
24
25namespace
26{
27
28TokenResult extractTokens(QKeychain::Job *job)
29{
30 auto readJob = static_cast<QKeychain::ReadPasswordJob *>(job);
31 QDataStream stream(readJob->binaryData());
32 QVariantMap map;
33 stream >> map;
34
35 const auto accessToken = map.value(QStringLiteral("accessToken")).toString();
36 const auto refreshToken = map.value(QStringLiteral("refreshToken")).toString();
37 return {accessToken, refreshToken};
38}
39
40QByteArray serializeTokens(const TokenResult &result)
41{
42 QVariantMap map = {{QStringLiteral("accessToken"), result.accessToken()}, {QStringLiteral("refreshToken"), result.refreshToken()}};
43 QByteArray data;
45 stream << map;
46 return data;
47}
48
49} // namespace
50
51OutlookPasswordRequester::OutlookPasswordRequester(Transport *transport, QObject *parent)
52 : XOAuthPasswordRequester(transport, parent)
53{
54}
55
56OutlookPasswordRequester::~OutlookPasswordRequester() = default;
57
58void OutlookPasswordRequester::requestPassword(bool forceRefresh)
59{
60 auto job = new QKeychain::ReadPasswordJob(QStringLiteral("mailtransports"));
61 job->setKey(QString::number(transport()->id()));
62 connect(job, &QKeychain::ReadPasswordJob::finished, this, [this, forceRefresh](QKeychain::Job *job) {
63 if (job->error() == QKeychain::Error::EntryNotFound) {
64 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 token found in keychain, requesting new token...";
65 mTokenRequester = std::make_unique<OutlookOAuthTokenRequester>(clientId, tenantId, scopes);
66 connect(mTokenRequester.get(), &OutlookOAuthTokenRequester::finished, this, &OutlookPasswordRequester::onTokenRequestFinished);
67 mTokenRequester->requestToken(transport()->userName());
68 return;
69 }
70
71 if (job->error() != QKeychain::Error::NoError) {
72 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Failed to read Outlook OAuth2 token from keychain:" << job->errorString();
73 Q_EMIT done(Error, {});
74 return;
75 }
76
77 mTokenRequester = std::make_unique<OutlookOAuthTokenRequester>(clientId, tenantId, scopes);
78 connect(mTokenRequester.get(), &OutlookOAuthTokenRequester::finished, this, &OutlookPasswordRequester::onTokenRequestFinished);
79
80 const auto tokens = extractTokens(job);
81 if (tokens.accessToken().isEmpty()) {
82 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 access token found in keychain, requesting new token...";
83 mTokenRequester->requestToken(transport()->userName());
84 } else if (forceRefresh) {
85 if (!tokens.refreshToken().isEmpty()) {
86 qCDebug(MAILTRANSPORT_SMTP_LOG) << "Found an Outlook OAuth2 refresh token in keychain, refreshing access token...";
87 mTokenRequester->refreshToken(tokens.refreshToken());
88 } else {
89 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 refresh token found in keychain, requesting new token...";
90 mTokenRequester->requestToken(transport()->userName());
91 }
92 } else {
93 qCDebug(MAILTRANSPORT_SMTP_LOG) << "Found an Outlook OAuth2 access token in KWallet, using it...";
94 Q_EMIT done(PasswordRetrieved, tokens.accessToken());
95 }
96 });
97 job->start();
98}
99
100void OutlookPasswordRequester::onTokenRequestFinished(const TokenResult &result)
101{
102 if (result.hasError()) {
103 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Error obtaining Outlook OAuth2 token:" << result.errorText();
104 Q_EMIT done(Error, {});
105 return;
106 }
107
108 auto job = new QKeychain::WritePasswordJob(QStringLiteral("mailtransports"));
109 job->setKey(QString::number(transport()->id()));
110 job->setBinaryData(serializeTokens(result));
111 connect(job, &QKeychain::WritePasswordJob::finished, this, [result](QKeychain::Job *job) {
112 if (job->error() != QKeychain::Error::NoError) {
113 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Failed to store Outlook OAuth2 token to keychain:" << job->errorString();
114 }
115 });
116 job->start();
117
118 const QString tokens = QStringLiteral("%1\001%2").arg(result.accessToken(), result.refreshToken());
119 Q_EMIT done(PasswordRetrieved, tokens);
120}
121
122#include "moc_outlookpasswordrequester.cpp"
Represents the settings of a specific mail transport.
Definition transport.h:33
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:43:20 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.