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 "transport.h"
11
12#include <QDataStream>
13#include <QVariantMap>
14
15#include <memory>
16#include <qt6keychain/keychain.h>
17
18using namespace MailTransport;
19
20static const QString clientId = QStringLiteral("18da2bc3-146a-4581-8c92-27dc7b9954a0");
21static const QString tenantId = QStringLiteral("common");
22static const QStringList scopes = {QStringLiteral("https://outlook.office.com/SMTP.Send"), QStringLiteral("offline_access")};
23
24namespace
25{
26
27TokenResult extractTokens(QKeychain::Job *job)
28{
29 auto readJob = static_cast<QKeychain::ReadPasswordJob *>(job);
30 QDataStream stream(readJob->binaryData());
31 QVariantMap map;
32 stream >> map;
33
34 const auto accessToken = map.value(QStringLiteral("accessToken")).toString();
35 const auto refreshToken = map.value(QStringLiteral("refreshToken")).toString();
36 return {accessToken, refreshToken};
37}
38
39QByteArray serializeTokens(const TokenResult &result)
40{
41 QVariantMap map = {{QStringLiteral("accessToken"), result.accessToken()}, {QStringLiteral("refreshToken"), result.refreshToken()}};
42 QByteArray data;
44 stream << map;
45 return data;
46}
47
48} // namespace
49
50OutlookPasswordRequester::OutlookPasswordRequester(Transport *transport, QObject *parent)
51 : XOAuthPasswordRequester(transport, parent)
52{
53}
54
55OutlookPasswordRequester::~OutlookPasswordRequester() = default;
56
57void OutlookPasswordRequester::requestPassword(bool forceRefresh)
58{
59 auto job = new QKeychain::ReadPasswordJob(QStringLiteral("mailtransports"));
60 job->setKey(QString::number(transport()->id()));
61 connect(job, &QKeychain::ReadPasswordJob::finished, this, [this, forceRefresh](QKeychain::Job *job) {
62 if (job->error() == QKeychain::Error::EntryNotFound) {
63 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 token found in keychain, requesting new token...";
64 mTokenRequester = std::make_unique<OutlookOAuthTokenRequester>(clientId, tenantId, scopes);
65 connect(mTokenRequester.get(), &OutlookOAuthTokenRequester::finished, this, &OutlookPasswordRequester::onTokenRequestFinished);
66 mTokenRequester->requestToken(transport()->userName());
67 return;
68 }
69
70 if (job->error() != QKeychain::Error::NoError) {
71 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Failed to read Outlook OAuth2 token from keychain:" << job->errorString();
72 Q_EMIT done(Error, {});
73 return;
74 }
75
76 mTokenRequester = std::make_unique<OutlookOAuthTokenRequester>(clientId, tenantId, scopes);
77 connect(mTokenRequester.get(), &OutlookOAuthTokenRequester::finished, this, &OutlookPasswordRequester::onTokenRequestFinished);
78
79 const auto tokens = extractTokens(job);
80 if (tokens.accessToken().isEmpty()) {
81 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 access token found in keychain, requesting new token...";
82 mTokenRequester->requestToken(transport()->userName());
83 } else if (forceRefresh) {
84 if (!tokens.refreshToken().isEmpty()) {
85 qCDebug(MAILTRANSPORT_SMTP_LOG) << "Found an Outlook OAuth2 refresh token in keychain, refreshing access token...";
86 mTokenRequester->refreshToken(tokens.refreshToken());
87 } else {
88 qCDebug(MAILTRANSPORT_SMTP_LOG) << "No Outlook OAuth2 refresh token found in keychain, requesting new token...";
89 mTokenRequester->requestToken(transport()->userName());
90 }
91 } else {
92 qCDebug(MAILTRANSPORT_SMTP_LOG) << "Found an Outlook OAuth2 access token in KWallet, using it...";
93 Q_EMIT done(PasswordRetrieved, tokens.accessToken());
94 }
95 });
96 job->start();
97}
98
99void OutlookPasswordRequester::onTokenRequestFinished(const TokenResult &result)
100{
101 if (result.hasError()) {
102 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Error obtaining Outlook OAuth2 token:" << result.errorText();
103 Q_EMIT done(Error, {});
104 return;
105 }
106
107 auto job = new QKeychain::WritePasswordJob(QStringLiteral("mailtransports"));
108 job->setKey(QString::number(transport()->id()));
109 job->setBinaryData(serializeTokens(result));
110 connect(job, &QKeychain::WritePasswordJob::finished, this, [result](QKeychain::Job *job) {
111 if (job->error() != QKeychain::Error::NoError) {
112 qCWarning(MAILTRANSPORT_SMTP_LOG) << "Failed to store Outlook OAuth2 token to keychain:" << job->errorString();
113 }
114 });
115 job->start();
116
117 const QString tokens = QStringLiteral("%1\001%2").arg(result.accessToken(), result.refreshToken());
118 Q_EMIT done(PasswordRetrieved, tokens);
119}
120
121#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 Mon Nov 4 2024 16:35:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.