1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <qoauth2authorizationcodeflow.h>
5#include <private/qoauth2authorizationcodeflow_p.h>
6
7#include <qmap.h>
8#include <qurl.h>
9#include <qvariant.h>
10#include <qurlquery.h>
11#include <qjsonobject.h>
12#include <qjsondocument.h>
13#include <qauthenticator.h>
14#include <qoauthhttpserverreplyhandler.h>
15
16#include <QtCore/qcryptographichash.h>
17
18#include <functional>
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24/*!
25 \class QOAuth2AuthorizationCodeFlow
26 \inmodule QtNetworkAuth
27 \ingroup oauth
28 \brief The QOAuth2AuthorizationCodeFlow class provides an
29 implementation of the
30 \l {https://tools.ietf.org/html/rfc6749#section-4.1}
31 {Authorization Code Grant} flow.
32 \since 5.8
33
34 This class implements the
35 \l {https://tools.ietf.org/html/rfc6749#section-4.1}
36 {Authorization Code Grant} flow, which is used both to obtain and
37 to refresh access tokens. It is a redirection-based flow so the
38 user will need access to a web browser.
39
40 As a redirection-based flow this class requires a proper
41 reply handler to be set. See \l {Qt OAuth2 Overview},
42 QOAuthHttpServerReplyHandler, and QOAuthUriSchemeReplyHandler.
43*/
44
45/*!
46 \property QOAuth2AuthorizationCodeFlow::accessTokenUrl
47 \brief This property holds the URL used to convert the temporary
48 code received during the authorization response.
49
50 \b {See also}:
51 \l {https://tools.ietf.org/html/rfc6749#section-4.1.3}{Access
52 Token Request}
53*/
54
55QOAuth2AuthorizationCodeFlowPrivate::QOAuth2AuthorizationCodeFlowPrivate(
56 const QUrl &authorizationUrl, const QUrl &accessTokenUrl, const QString &clientIdentifier,
57 QNetworkAccessManager *manager) :
58 QAbstractOAuth2Private(std::make_pair(x: clientIdentifier, y: QString()), authorizationUrl, manager),
59 accessTokenUrl(accessTokenUrl)
60{
61 responseType = QStringLiteral("code");
62}
63
64static QString toUrlFormEncoding(const QString &source)
65{
66 // RFC 6749 Appendix B
67 // https://datatracker.ietf.org/doc/html/rfc6749#appendix-B
68 // Replace spaces with plus, while percent-encoding the rest
69 QByteArray encoded = source.toUtf8().toPercentEncoding(exclude: " ");
70 encoded.replace(before: " ", after: "+");
71 return QString::fromUtf8(ba: encoded);
72}
73
74static QString fromUrlFormEncoding(const QString &source)
75{
76 QByteArray decoded = source.toUtf8();
77 decoded = QByteArray::fromPercentEncoding(pctEncoded: decoded.replace(before: "+",after: " "));
78 return QString::fromUtf8(ba: decoded);
79}
80
81void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &data)
82{
83 Q_Q(QOAuth2AuthorizationCodeFlow);
84 using Key = QAbstractOAuth2Private::OAuth2KeyString;
85
86 if (status != QAbstractOAuth::Status::NotAuthenticated) {
87 qCWarning(loggingCategory) << "Authorization stage: callback in unexpected status:"
88 << static_cast<int>(status) << ", ignoring the callback";
89 return;
90 }
91
92 Q_ASSERT(!state.isEmpty());
93
94 const QString error = data.value(key: Key::error).toString();
95 const QString code = data.value(key: Key::code).toString();
96 const QString receivedState = fromUrlFormEncoding(source: data.value(key: Key::state).toString());
97
98 if (error.size()) {
99 // RFC 6749, Section 5.2 Error Response
100 const QString uri = data.value(key: Key::errorUri).toString();
101 const QString description = data.value(key: Key::errorDescription).toString();
102 qCWarning(loggingCategory, "Authorization stage: AuthenticationError: %s(%s): %s",
103 qPrintable(error), qPrintable(uri), qPrintable(description));
104 Q_EMIT q->error(error, errorDescription: description, uri);
105 // Emit also requestFailed() so that it is a signal for all errors
106 emit q->requestFailed(error: QAbstractOAuth::Error::ServerError);
107 return;
108 }
109
110 if (code.isEmpty()) {
111 qCWarning(loggingCategory, "Authorization stage: Code not received");
112 emit q->requestFailed(error: QAbstractOAuth::Error::OAuthTokenNotFoundError);
113 return;
114 }
115 if (receivedState.isEmpty()) {
116 qCWarning(loggingCategory, "Authorization stage: State not received");
117 emit q->requestFailed(error: QAbstractOAuth::Error::ServerError);
118 return;
119 }
120 if (state != receivedState) {
121 qCWarning(loggingCategory) << "Authorization stage: State mismatch";
122 emit q->requestFailed(error: QAbstractOAuth::Error::ServerError);
123 return;
124 }
125
126 setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived);
127
128 QVariantMap copy(data);
129 copy.remove(key: Key::code);
130 setExtraTokens(copy);
131 q->requestAccessToken(code);
132}
133
134void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QVariantMap &values)
135{
136 Q_Q(QOAuth2AuthorizationCodeFlow);
137 using Key = QAbstractOAuth2Private::OAuth2KeyString;
138
139 if (values.contains(key: Key::error)) {
140 _q_accessTokenRequestFailed(error: QAbstractOAuth::Error::ServerError,
141 errorString: values.value(key: Key::error).toString());
142 return;
143 }
144
145 bool ok;
146 const QString accessToken = values.value(key: Key::accessToken).toString();
147 tokenType = values.value(key: Key::tokenType).toString();
148 int expiresIn = values.value(key: Key::expiresIn).toInt(ok: &ok);
149 if (!ok)
150 expiresIn = -1;
151 if (values.value(key: Key::refreshToken).isValid())
152 q->setRefreshToken(values.value(key: Key::refreshToken).toString());
153
154 if (accessToken.isEmpty()) {
155 _q_accessTokenRequestFailed(error: QAbstractOAuth::Error::OAuthTokenNotFoundError,
156 errorString: "Access token not received"_L1);
157 return;
158 }
159 q->setToken(accessToken);
160
161 // RFC 6749 section 5.1 https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
162 // If the requested scope and granted scopes differ, server is REQUIRED to return
163 // the scope. If OTOH the scopes match, the server MAY omit the scope in the response,
164 // in which case we assume that the granted scope matches the requested scope.
165 const QString scope = values.value(key: Key::scope).toString();
166 if (!scope.isEmpty())
167 q->setScope(scope);
168
169 if (expiresIn > 0)
170 setExpiresAt(QDateTime::currentDateTimeUtc().addSecs(secs: expiresIn));
171 else
172 setExpiresAt(QDateTime());
173
174 QVariantMap copy(values);
175 copy.remove(key: Key::accessToken);
176 copy.remove(key: Key::expiresIn);
177 copy.remove(key: Key::refreshToken);
178 copy.remove(key: Key::scope);
179 copy.remove(key: Key::tokenType);
180 QVariantMap newExtraTokens = extraTokens;
181 newExtraTokens.insert(map: copy);
182 setExtraTokens(newExtraTokens);
183
184 setStatus(QAbstractOAuth::Status::Granted);
185}
186
187void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed(QAbstractOAuth::Error error,
188 const QString& errorString)
189{
190 Q_Q(QOAuth2AuthorizationCodeFlow);
191 qCWarning(loggingCategory) << "Token request failed:" << errorString;
192 // If we were refreshing, reset status to Granted if we have an access token.
193 // The access token might still be valid, and even if it wouldn't be,
194 // refreshing can be attempted again.
195 if (q->status() == QAbstractOAuth::Status::RefreshingToken) {
196 if (!q->token().isEmpty())
197 setStatus(QAbstractOAuth::Status::Granted);
198 else
199 setStatus(QAbstractOAuth::Status::NotAuthenticated);
200 }
201 emit q->requestFailed(error);
202}
203
204void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply,
205 QAuthenticator *authenticator)
206{
207 if (reply == currentReply){
208 const auto url = reply->url();
209 if (url == accessTokenUrl) {
210 authenticator->setUser(clientIdentifier);
211 authenticator->setPassword(QString());
212 }
213 }
214}
215
216/*
217 Creates and returns a new PKCE 'code_challenge', and stores the
218 underlying 'code_verifier' that was used to compute it.
219
220 The PKCE flow involves two parts:
221 1. Authorization request: include the 'code_challenge' which
222 is computed from the 'code_verifier'.
223 2. Access token request: include the original 'code_verifier'.
224
225 With these two parts the authorization server is able to verify
226 that the token request came from same entity as the original
227 authorization request, mitigating the risk of authorization code
228 interception attacks.
229*/
230QByteArray QOAuth2AuthorizationCodeFlowPrivate::createPKCEChallenge()
231{
232 switch (pkceMethod) {
233 case QOAuth2AuthorizationCodeFlow::PkceMethod::None:
234 pkceCodeVerifier.clear();
235 return {};
236 case QOAuth2AuthorizationCodeFlow::PkceMethod::Plain:
237 // RFC 7636 4.2, plain
238 // code_challenge = code_verifier
239 pkceCodeVerifier = generateRandomBase64String(length: pkceVerifierLength);
240 return pkceCodeVerifier;
241 case QOAuth2AuthorizationCodeFlow::PkceMethod::S256:
242 // RFC 7636 4.2, S256
243 // code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
244 pkceCodeVerifier = generateRandomBase64String(length: pkceVerifierLength);
245 // RFC 7636 3. Terminology:
246 // "with all trailing '=' characters omitted"
247 return QCryptographicHash::hash(data: pkceCodeVerifier, method: QCryptographicHash::Algorithm::Sha256)
248 .toBase64(options: QByteArray::Base64Option::Base64UrlEncoding
249 | QByteArray::Base64Option::OmitTrailingEquals);
250 };
251 Q_UNREACHABLE_RETURN({});
252}
253
254/*!
255 Constructs a QOAuth2AuthorizationCodeFlow object with parent
256 object \a parent.
257*/
258QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QObject *parent) :
259 QOAuth2AuthorizationCodeFlow(nullptr,
260 parent)
261{}
262
263/*!
264 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
265 as parent and sets \a manager as the network access manager.
266*/
267QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QNetworkAccessManager *manager,
268 QObject *parent) :
269 QOAuth2AuthorizationCodeFlow(QString(),
270 manager,
271 parent)
272{}
273
274/*!
275 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
276 as parent and sets \a manager as the network access manager. The
277 client identifier is set to \a clientIdentifier.
278*/
279QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
280 QNetworkAccessManager *manager,
281 QObject *parent) :
282 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(QUrl(), QUrl(), clientIdentifier,
283 manager),
284 parent)
285{}
286
287/*!
288 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
289 as parent and sets \a manager as the network access manager. The
290 authenticate URL is set to \a authenticateUrl and the access
291 token URL is set to \a accessTokenUrl.
292*/
293QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QUrl &authenticateUrl,
294 const QUrl &accessTokenUrl,
295 QNetworkAccessManager *manager,
296 QObject *parent) :
297 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl,
298 QString(), manager),
299 parent)
300{}
301
302/*!
303 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
304 as parent and sets \a manager as the network access manager. The
305 client identifier is set to \a clientIdentifier the authenticate
306 URL is set to \a authenticateUrl and the access token URL is set
307 to \a accessTokenUrl.
308*/
309QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
310 const QUrl &authenticateUrl,
311 const QUrl &accessTokenUrl,
312 QNetworkAccessManager *manager,
313 QObject *parent) :
314 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl,
315 clientIdentifier, manager),
316 parent)
317{}
318
319/*!
320 Destroys the QOAuth2AuthorizationCodeFlow instance.
321*/
322QOAuth2AuthorizationCodeFlow::~QOAuth2AuthorizationCodeFlow()
323{}
324
325/*!
326 Returns the URL used to request the access token.
327 \sa setAccessTokenUrl()
328*/
329QUrl QOAuth2AuthorizationCodeFlow::accessTokenUrl() const
330{
331 Q_D(const QOAuth2AuthorizationCodeFlow);
332 return d->accessTokenUrl;
333}
334
335/*!
336 Sets the URL used to request the access token to
337 \a accessTokenUrl.
338*/
339void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &accessTokenUrl)
340{
341 Q_D(QOAuth2AuthorizationCodeFlow);
342 if (d->accessTokenUrl != accessTokenUrl) {
343 d->accessTokenUrl = accessTokenUrl;
344 Q_EMIT accessTokenUrlChanged(accessTokenUrl);
345 }
346}
347
348/*!
349 \enum QOAuth2AuthorizationCodeFlow::PkceMethod
350 \since 6.8
351
352 List of available \l {https://datatracker.ietf.org/doc/html/rfc7636}
353 {Proof Key for Code Exchange (PKCE) methods}.
354
355 PKCE is a security measure to mitigate the risk of \l
356 {https://datatracker.ietf.org/doc/html/rfc7636#section-1}{authorization
357 code interception attacks}. As such it is relevant for OAuth2
358 "Authorization Code" flow (grant) and in particular with
359 native applications.
360
361 PKCE inserts additional parameters into authorization
362 and access token requests. With the help of these parameters the
363 authorization server is able to verify that an access token request
364 originates from the same entity that issued the authorization
365 request.
366
367 \value None PKCE is not used.
368 \value Plain The Plain PKCE method is used. Use this only if it is not
369 possible to use S256. With Plain method the
370 \l {https://datatracker.ietf.org/doc/html/rfc7636#section-4.2}{code challenge}
371 equals to the
372 \l {https://datatracker.ietf.org/doc/html/rfc7636#section-4.1}{code verifier}.
373 \value S256 The S256 PKCE method is used. This is the default and the
374 recommended method for native applications. With the S256 method
375 the \e {code challenge} is a base64url-encoded value of the
376 SHA-256 of the \e {code verifier}.
377
378 \sa setPkceMethod(), pkceMethod()
379*/
380
381/*!
382 \since 6.8
383
384 Sets the current PKCE method to \a method.
385
386 Optionally, the \a length parameter can be used to set the length
387 of the \c code_verifier. The value must be between 43 and 128 bytes.
388 The 'code verifier' itself is random-generated by the library.
389
390 \sa pkceMethod(), QOAuth2AuthorizationCodeFlow::PkceMethod
391*/
392void QOAuth2AuthorizationCodeFlow::setPkceMethod(PkceMethod method, qsizetype length)
393{
394 Q_D(QOAuth2AuthorizationCodeFlow);
395 if (length < 43 || length > 128) {
396 // RFC 7636 Section 4.1, the code_verifer should be 43..128 bytes
397 qWarning(msg: "Invalid PKCE length provided, must be between 43..128. Ignoring.");
398 return;
399 }
400 static_assert(std::is_same_v<decltype(d->pkceVerifierLength), quint8>);
401 d->pkceVerifierLength = quint8(length);
402 d->pkceMethod = method;
403}
404
405/*!
406 \since 6.8
407
408 Returns the current PKCE method.
409
410 \sa setPkceMethod(), QOAuth2AuthorizationCodeFlow::PkceMethod
411*/
412QOAuth2AuthorizationCodeFlow::PkceMethod QOAuth2AuthorizationCodeFlow::pkceMethod() const noexcept
413{
414 Q_D(const QOAuth2AuthorizationCodeFlow);
415 return d->pkceMethod;
416}
417
418/*!
419 Starts the authentication flow as described in
420 \l {https://tools.ietf.org/html/rfc6749#section-4.1}{The OAuth
421 2.0 Authorization Framework}
422*/
423void QOAuth2AuthorizationCodeFlow::grant()
424{
425 Q_D(QOAuth2AuthorizationCodeFlow);
426 if (d->authorizationUrl.isEmpty()) {
427 qCWarning(d->loggingCategory, "No authenticate Url set");
428 return;
429 }
430 if (d->accessTokenUrl.isEmpty()) {
431 qCWarning(d->loggingCategory, "No request access token Url set");
432 return;
433 }
434
435 resourceOwnerAuthorization(url: d->authorizationUrl);
436}
437
438/*!
439 Call this function to refresh the token. Access tokens are not
440 permanent. After a time specified along with the access token
441 when it was obtained, the access token will become invalid.
442
443 If refreshing the token fails and an access token exists, the status is
444 set to QAbstractOAuth::Status::Granted, and to
445 QAbstractOAuth::Status::NotAuthenticated otherwise.
446
447 \sa QAbstractOAuth::requestFailed()
448 \sa {https://tools.ietf.org/html/rfc6749#section-1.5}{Refresh
449 Token}
450*/
451void QOAuth2AuthorizationCodeFlow::refreshAccessToken()
452{
453 Q_D(QOAuth2AuthorizationCodeFlow);
454
455 if (d->refreshToken.isEmpty()) {
456 qCWarning(d->loggingCategory, "Cannot refresh access token. Empty refresh token");
457 return;
458 }
459 if (d->status == Status::RefreshingToken) {
460 qCWarning(d->loggingCategory, "Cannot refresh access token. "
461 "Refresh Access Token is already in progress");
462 return;
463 }
464
465 using Key = QAbstractOAuth2Private::OAuth2KeyString;
466
467 QMultiMap<QString, QVariant> parameters;
468 QNetworkRequest request(d->accessTokenUrl);
469#ifndef QT_NO_SSL
470 if (d->sslConfiguration && !d->sslConfiguration->isNull())
471 request.setSslConfiguration(*d->sslConfiguration);
472#endif
473 QUrlQuery query;
474 parameters.insert(key: Key::grantType, QStringLiteral("refresh_token"));
475 parameters.insert(key: Key::refreshToken, value: d->refreshToken);
476 parameters.insert(key: Key::clientIdentifier, value: d->clientIdentifier);
477 parameters.insert(key: Key::clientSharedSecret, value: d->clientIdentifierSharedKey);
478 if (d->modifyParametersFunction)
479 d->modifyParametersFunction(Stage::RefreshingAccessToken, &parameters);
480 query = QAbstractOAuthPrivate::createQuery(parameters);
481 request.setHeader(header: QNetworkRequest::ContentTypeHeader,
482 QStringLiteral("application/x-www-form-urlencoded"));
483
484 const QString data = query.toString(encoding: QUrl::FullyEncoded);
485 d->currentReply = d->networkAccessManager()->post(request, data: data.toUtf8());
486 setStatus(Status::RefreshingToken);
487
488 QNetworkReply *reply = d->currentReply.data();
489 QAbstractOAuthReplyHandler *handler = replyHandler();
490 connect(sender: reply, signal: &QNetworkReply::finished, context: handler,
491 slot: [handler, reply]() { handler->networkReplyFinished(reply); });
492 connect(sender: reply, signal: &QNetworkReply::finished, context: reply, slot: &QNetworkReply::deleteLater);
493 QObjectPrivate::connect(sender: handler, signal: &QAbstractOAuthReplyHandler::tokensReceived, receiverPrivate: d,
494 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished,
495 type: Qt::UniqueConnection);
496 QObjectPrivate::connect(sender: d->networkAccessManager(),
497 signal: &QNetworkAccessManager::authenticationRequired,
498 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
499 type: Qt::UniqueConnection);
500 QObjectPrivate::connect(sender: handler, signal: &QAbstractOAuthReplyHandler::tokenRequestErrorOccurred,
501 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed,
502 type: Qt::UniqueConnection);
503}
504
505/*!
506 Generates an authentication URL to be used in the
507 \l {https://tools.ietf.org/html/rfc6749#section-4.1.1}
508 {Authorization Request} using \a parameters.
509*/
510QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QMultiMap<QString, QVariant> &parameters)
511{
512 Q_D(QOAuth2AuthorizationCodeFlow);
513 using Key = QAbstractOAuth2Private::OAuth2KeyString;
514
515 if (d->state.isEmpty())
516 setState(QAbstractOAuth2Private::generateRandomState());
517 Q_ASSERT(!d->state.isEmpty());
518 const QString state = d->state;
519
520 QMultiMap<QString, QVariant> p(parameters);
521 QUrl url(d->authorizationUrl);
522 p.insert(key: Key::responseType, value: responseType());
523 p.insert(key: Key::clientIdentifier, value: d->clientIdentifier);
524 p.insert(key: Key::redirectUri, value: callback());
525 p.insert(key: Key::scope, value: d->scope);
526 p.insert(key: Key::state, value: toUrlFormEncoding(source: state));
527 if (d->pkceMethod != PkceMethod::None) {
528 p.insert(key: Key::codeChallenge, value: d->createPKCEChallenge());
529 p.insert(key: Key::codeChallengeMethod,
530 value: d->pkceMethod == PkceMethod::Plain ? u"plain"_s : u"S256"_s);
531 }
532 if (d->modifyParametersFunction)
533 d->modifyParametersFunction(Stage::RequestingAuthorization, &p);
534 url.setQuery(d->createQuery(parameters: p));
535 connect(sender: replyHandler(), signal: &QAbstractOAuthReplyHandler::callbackReceived, context: this,
536 slot: &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, type: Qt::UniqueConnection);
537 setStatus(QAbstractOAuth::Status::NotAuthenticated);
538 qCDebug(d->loggingCategory, "Authorization URL generated");
539 return url;
540}
541
542/*!
543 Requests an access token from the received \a code. The \a code
544 is received as a response when the user completes a successful
545 authentication in the browser.
546*/
547void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code)
548{
549 Q_D(QOAuth2AuthorizationCodeFlow);
550 using Key = QAbstractOAuth2Private::OAuth2KeyString;
551
552 QMultiMap<QString, QVariant> parameters;
553 QNetworkRequest request(d->accessTokenUrl);
554#ifndef QT_NO_SSL
555 if (d->sslConfiguration && !d->sslConfiguration->isNull())
556 request.setSslConfiguration(*d->sslConfiguration);
557#endif
558 QUrlQuery query;
559 parameters.insert(key: Key::grantType, QStringLiteral("authorization_code"));
560
561 if (code.contains(c: QLatin1Char('%')))
562 parameters.insert(key: Key::code, value: code);
563 else
564 parameters.insert(key: Key::code, value: QUrl::toPercentEncoding(code));
565
566 parameters.insert(key: Key::redirectUri, value: QUrl::toPercentEncoding(callback()));
567 parameters.insert(key: Key::clientIdentifier, value: QUrl::toPercentEncoding(d->clientIdentifier));
568
569 if (d->pkceMethod != PkceMethod::None)
570 parameters.insert(key: Key::codeVerifier, value: d->pkceCodeVerifier);
571 if (!d->clientIdentifierSharedKey.isEmpty())
572 parameters.insert(key: Key::clientSharedSecret, value: d->clientIdentifierSharedKey);
573 if (d->modifyParametersFunction)
574 d->modifyParametersFunction(Stage::RequestingAccessToken, &parameters);
575 query = QAbstractOAuthPrivate::createQuery(parameters);
576 request.setHeader(header: QNetworkRequest::ContentTypeHeader,
577 QStringLiteral("application/x-www-form-urlencoded"));
578
579 const QByteArray data = query.toString(encoding: QUrl::FullyEncoded).toLatin1();
580 QNetworkReply *reply = d->networkAccessManager()->post(request, data);
581 d->currentReply = reply;
582 QAbstractOAuthReplyHandler *handler = replyHandler();
583 QObject::connect(sender: reply, signal: &QNetworkReply::finished, context: handler,
584 slot: [handler, reply] { handler->networkReplyFinished(reply); });
585 connect(sender: reply, signal: &QNetworkReply::finished, context: reply, slot: &QNetworkReply::deleteLater);
586 QObjectPrivate::connect(sender: handler, signal: &QAbstractOAuthReplyHandler::tokensReceived, receiverPrivate: d,
587 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished,
588 type: Qt::UniqueConnection);
589 QObjectPrivate::connect(sender: d->networkAccessManager(),
590 signal: &QNetworkAccessManager::authenticationRequired,
591 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
592 type: Qt::UniqueConnection);
593 QObjectPrivate::connect(sender: handler,
594 signal: &QAbstractOAuthReplyHandler::tokenRequestErrorOccurred,
595 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed,
596 type: Qt::UniqueConnection);
597}
598
599/*!
600 Builds an authentication URL using \a url and \a parameters. This
601 function emits an authorizeWithBrowser() signal to require user
602 interaction.
603*/
604void QOAuth2AuthorizationCodeFlow::resourceOwnerAuthorization(const QUrl &url,
605 const QMultiMap<QString, QVariant> &parameters)
606{
607 Q_D(QOAuth2AuthorizationCodeFlow);
608 if (Q_UNLIKELY(url != d->authorizationUrl)) {
609 qCWarning(d->loggingCategory, "Invalid URL: %s", qPrintable(url.toString()));
610 return;
611 }
612 const QUrl u = buildAuthenticateUrl(parameters);
613 QObjectPrivate::connect(sender: this, signal: &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, receiverPrivate: d,
614 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback,
615 type: Qt::UniqueConnection);
616 Q_EMIT authorizeWithBrowser(url: u);
617}
618
619QT_END_NAMESPACE
620
621#include "moc_qoauth2authorizationcodeflow.cpp"
622

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtnetworkauth/src/oauth/qoauth2authorizationcodeflow.cpp