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

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