1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Network Auth module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#ifndef QT_NO_HTTP
31
32#include <qoauth2authorizationcodeflow.h>
33#include <private/qoauth2authorizationcodeflow_p.h>
34
35#include <qmap.h>
36#include <qurl.h>
37#include <qvariant.h>
38#include <qurlquery.h>
39#include <qjsonobject.h>
40#include <qjsondocument.h>
41#include <qauthenticator.h>
42#include <qoauthhttpserverreplyhandler.h>
43
44#include <functional>
45
46QT_BEGIN_NAMESPACE
47
48/*!
49 \class QOAuth2AuthorizationCodeFlow
50 \inmodule QtNetworkAuth
51 \ingroup oauth
52 \brief The QOAuth2AuthorizationCodeFlow class provides an
53 implementation of the
54 \l {https://tools.ietf.org/html/rfc6749#section-4.1}
55 {Authorization Code Grant} flow.
56 \since 5.8
57
58 This class implements the
59 \l {https://tools.ietf.org/html/rfc6749#section-4.1}
60 {Authorization Code Grant} flow, which is used both to obtain and
61 to refresh access tokens. It is a redirection-based flow so the
62 user will need access to a web browser.
63*/
64
65/*!
66 \property QOAuth2AuthorizationCodeFlow::accessTokenUrl
67 \brief This property holds the URL used to convert the temporary
68 code received during the authorization response.
69
70 \b {See also}:
71 \l {https://tools.ietf.org/html/rfc6749#section-4.1.3}{Access
72 Token Request}
73*/
74
75QOAuth2AuthorizationCodeFlowPrivate::QOAuth2AuthorizationCodeFlowPrivate(
76 const QUrl &authorizationUrl, const QUrl &accessTokenUrl, const QString &clientIdentifier,
77 QNetworkAccessManager *manager) :
78 QAbstractOAuth2Private(qMakePair(x: clientIdentifier, y: QString()), authorizationUrl, manager),
79 accessTokenUrl(accessTokenUrl)
80{
81 responseType = QStringLiteral("code");
82}
83
84void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &data)
85{
86 Q_Q(QOAuth2AuthorizationCodeFlow);
87 using Key = QAbstractOAuth2Private::OAuth2KeyString;
88
89 if (status != QAbstractOAuth::Status::NotAuthenticated) {
90 qCWarning(loggingCategory, "Unexpected call");
91 return;
92 }
93
94 Q_ASSERT(!state.isEmpty());
95
96 const QString error = data.value(akey: Key::error).toString();
97 const QString code = data.value(akey: Key::code).toString();
98 const QString receivedState = data.value(akey: Key::state).toString();
99 if (error.size()) {
100 const QString uri = data.value(akey: Key::errorUri).toString();
101 const QString description = data.value(akey: Key::errorDescription).toString();
102 qCWarning(loggingCategory, "AuthenticationError: %s(%s): %s",
103 qPrintable(error), qPrintable(uri), qPrintable(description));
104 Q_EMIT q->error(error, errorDescription: description, uri);
105 return;
106 }
107 if (code.isEmpty()) {
108 qCWarning(loggingCategory, "AuthenticationError: Code not received");
109 return;
110 }
111 if (receivedState.isEmpty()) {
112 qCWarning(loggingCategory, "State not received");
113 return;
114 }
115 if (state != receivedState) {
116 qCWarning(loggingCategory, "State mismatch");
117 return;
118 }
119
120 setStatus(QAbstractOAuth::Status::TemporaryCredentialsReceived);
121
122 QVariantMap copy(data);
123 copy.remove(akey: Key::code);
124 extraTokens = copy;
125 q->requestAccessToken(code);
126}
127
128void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QVariantMap &values)
129{
130 Q_Q(QOAuth2AuthorizationCodeFlow);
131 using Key = QAbstractOAuth2Private::OAuth2KeyString;
132
133 if (values.contains(akey: Key::error)) {
134 const QString error = values.value(akey: Key::error).toString();
135 qCWarning(loggingCategory, "Error: %s", qPrintable(error));
136 return;
137 }
138
139 bool ok;
140 const QString accessToken = values.value(akey: Key::accessToken).toString();
141 tokenType = values.value(akey: Key::tokenType).toString();
142 int expiresIn = values.value(akey: Key::expiresIn).toInt(ok: &ok);
143 if (!ok)
144 expiresIn = -1;
145 if (values.value(akey: Key::refreshToken).isValid())
146 q->setRefreshToken(values.value(akey: Key::refreshToken).toString());
147 scope = values.value(akey: Key::scope).toString();
148 if (accessToken.isEmpty()) {
149 qCWarning(loggingCategory, "Access token not received");
150 return;
151 }
152 q->setToken(accessToken);
153
154 const QDateTime currentDateTime = QDateTime::currentDateTime();
155 if (expiresIn > 0 && currentDateTime.secsTo(expiresAt) != expiresIn) {
156 expiresAt = currentDateTime.addSecs(secs: expiresIn);
157 Q_EMIT q->expirationAtChanged(expiration: expiresAt);
158 }
159
160 QVariantMap copy(values);
161 copy.remove(akey: Key::accessToken);
162 copy.remove(akey: Key::expiresIn);
163 copy.remove(akey: Key::refreshToken);
164 copy.remove(akey: Key::scope);
165 copy.remove(akey: Key::tokenType);
166 extraTokens.insert(map: copy);
167
168 setStatus(QAbstractOAuth::Status::Granted);
169}
170
171void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply,
172 QAuthenticator *authenticator)
173{
174 if (reply == currentReply){
175 const auto url = reply->url();
176 if (url == accessTokenUrl) {
177 authenticator->setUser(clientIdentifier);
178 authenticator->setPassword(QString());
179 }
180 }
181}
182
183/*!
184 Constructs a QOAuth2AuthorizationCodeFlow object with parent
185 object \a parent.
186*/
187QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QObject *parent) :
188 QOAuth2AuthorizationCodeFlow(nullptr,
189 parent)
190{}
191
192/*!
193 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
194 as parent and sets \a manager as the network access manager.
195*/
196QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(QNetworkAccessManager *manager,
197 QObject *parent) :
198 QOAuth2AuthorizationCodeFlow(QString(),
199 manager,
200 parent)
201{}
202
203/*!
204 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
205 as parent and sets \a manager as the network access manager. The
206 client identifier is set to \a clientIdentifier.
207*/
208QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
209 QNetworkAccessManager *manager,
210 QObject *parent) :
211 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(QUrl(), QUrl(), clientIdentifier,
212 manager),
213 parent)
214{}
215
216/*!
217 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
218 as parent and sets \a manager as the network access manager. The
219 authenticate URL is set to \a authenticateUrl and the access
220 token URL is set to \a accessTokenUrl.
221*/
222QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QUrl &authenticateUrl,
223 const QUrl &accessTokenUrl,
224 QNetworkAccessManager *manager,
225 QObject *parent) :
226 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl,
227 QString(), manager),
228 parent)
229{}
230
231/*!
232 Constructs a QOAuth2AuthorizationCodeFlow object using \a parent
233 as parent and sets \a manager as the network access manager. The
234 client identifier is set to \a clientIdentifier the authenticate
235 URL is set to \a authenticateUrl and the access token URL is set
236 to \a accessTokenUrl.
237*/
238QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &clientIdentifier,
239 const QUrl &authenticateUrl,
240 const QUrl &accessTokenUrl,
241 QNetworkAccessManager *manager,
242 QObject *parent) :
243 QAbstractOAuth2(*new QOAuth2AuthorizationCodeFlowPrivate(authenticateUrl, accessTokenUrl,
244 clientIdentifier, manager),
245 parent)
246{}
247
248/*!
249 Destroys the QOAuth2AuthorizationCodeFlow instance.
250*/
251QOAuth2AuthorizationCodeFlow::~QOAuth2AuthorizationCodeFlow()
252{}
253
254/*!
255 Returns the URL used to request the access token.
256 \sa setAccessTokenUrl()
257*/
258QUrl QOAuth2AuthorizationCodeFlow::accessTokenUrl() const
259{
260 Q_D(const QOAuth2AuthorizationCodeFlow);
261 return d->accessTokenUrl;
262}
263
264/*!
265 Sets the URL used to request the access token to
266 \a accessTokenUrl.
267*/
268void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &accessTokenUrl)
269{
270 Q_D(QOAuth2AuthorizationCodeFlow);
271 if (d->accessTokenUrl != accessTokenUrl) {
272 d->accessTokenUrl = accessTokenUrl;
273 Q_EMIT accessTokenUrlChanged(accessTokenUrl);
274 }
275}
276
277/*!
278 Starts the authentication flow as described in
279 \l {https://tools.ietf.org/html/rfc6749#section-4.1}{The OAuth
280 2.0 Authorization Framework}
281*/
282void QOAuth2AuthorizationCodeFlow::grant()
283{
284 Q_D(QOAuth2AuthorizationCodeFlow);
285 if (d->authorizationUrl.isEmpty()) {
286 qCWarning(d->loggingCategory, "No authenticate Url set");
287 return;
288 }
289 if (d->accessTokenUrl.isEmpty()) {
290 qCWarning(d->loggingCategory, "No request access token Url set");
291 return;
292 }
293
294 resourceOwnerAuthorization(url: d->authorizationUrl);
295}
296
297/*!
298 Call this function to refresh the token. Access tokens are not
299 permanent. After a time specified along with the access token
300 when it was obtained, the access token will become invalid.
301
302 \b {See also}:
303 \l {https://tools.ietf.org/html/rfc6749#section-1.5}{Refresh
304 Token}
305*/
306void QOAuth2AuthorizationCodeFlow::refreshAccessToken()
307{
308 Q_D(QOAuth2AuthorizationCodeFlow);
309
310 if (d->refreshToken.isEmpty()) {
311 qCWarning(d->loggingCategory, "Cannot refresh access token. Empty refresh token");
312 return;
313 }
314 if (d->status == Status::RefreshingToken) {
315 qCWarning(d->loggingCategory, "Cannot refresh access token. "
316 "Refresh Access Token is already in progress");
317 return;
318 }
319
320 using Key = QAbstractOAuth2Private::OAuth2KeyString;
321
322 QVariantMap parameters;
323 QNetworkRequest request(d->accessTokenUrl);
324 QUrlQuery query;
325 parameters.insert(akey: Key::grantType, QStringLiteral("refresh_token"));
326 parameters.insert(akey: Key::refreshToken, avalue: d->refreshToken);
327 parameters.insert(akey: Key::redirectUri, avalue: QUrl::toPercentEncoding(callback()));
328 parameters.insert(akey: Key::clientIdentifier, avalue: d->clientIdentifier);
329 parameters.insert(akey: Key::clientSharedSecret, avalue: d->clientIdentifierSharedKey);
330 if (d->modifyParametersFunction)
331 d->modifyParametersFunction(Stage::RefreshingAccessToken, &parameters);
332 query = QAbstractOAuthPrivate::createQuery(parameters);
333 request.setHeader(header: QNetworkRequest::ContentTypeHeader,
334 QStringLiteral("application/x-www-form-urlencoded"));
335
336 const QString data = query.toString(encoding: QUrl::FullyEncoded);
337 d->currentReply = d->networkAccessManager()->post(request, data: data.toUtf8());
338 d->status = Status::RefreshingToken;
339
340 QNetworkReply *reply = d->currentReply.data();
341 QAbstractOAuthReplyHandler *handler = replyHandler();
342 connect(sender: reply, signal: &QNetworkReply::finished,
343 slot: [handler, reply]() { handler->networkReplyFinished(reply); });
344 connect(sender: reply, signal: &QNetworkReply::finished, receiver: reply, slot: &QNetworkReply::deleteLater);
345 QObjectPrivate::connect(sender: d->replyHandler.data(), signal: &QAbstractOAuthReplyHandler::tokensReceived, receiverPrivate: d,
346 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished,
347 type: Qt::UniqueConnection);
348 QObjectPrivate::connect(sender: d->networkAccessManager(),
349 signal: &QNetworkAccessManager::authenticationRequired,
350 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
351 type: Qt::UniqueConnection);
352}
353
354/*!
355 Generates an authentication URL to be used in the
356 \l {https://tools.ietf.org/html/rfc6749#section-4.1.1}
357 {Authorization Request} using \a parameters.
358*/
359QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QVariantMap &parameters)
360{
361 Q_D(QOAuth2AuthorizationCodeFlow);
362 using Key = QAbstractOAuth2Private::OAuth2KeyString;
363
364 if (d->state.isEmpty())
365 setState(QAbstractOAuth2Private::generateRandomState());
366 Q_ASSERT(!d->state.isEmpty());
367 const QString state = d->state;
368
369 QVariantMap p(parameters);
370 QUrl url(d->authorizationUrl);
371 p.insert(akey: Key::responseType, avalue: responseType());
372 p.insert(akey: Key::clientIdentifier, avalue: d->clientIdentifier);
373 p.insert(akey: Key::redirectUri, avalue: callback());
374 p.insert(akey: Key::scope, avalue: d->scope);
375 p.insert(akey: Key::state, avalue: state);
376 if (d->modifyParametersFunction)
377 d->modifyParametersFunction(Stage::RequestingAuthorization, &p);
378 url.setQuery(d->createQuery(parameters: p));
379 connect(sender: d->replyHandler.data(), signal: &QAbstractOAuthReplyHandler::callbackReceived, receiver: this,
380 slot: &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, type: Qt::UniqueConnection);
381 setStatus(QAbstractOAuth::Status::NotAuthenticated);
382 qCDebug(d->loggingCategory, "Generated URL: %s", qPrintable(url.toString()));
383 return url;
384}
385
386/*!
387 Requests an access token from the received \a code. The \a code
388 is received as a response when the user completes a successful
389 authentication in the browser.
390*/
391void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code)
392{
393 Q_D(QOAuth2AuthorizationCodeFlow);
394 using Key = QAbstractOAuth2Private::OAuth2KeyString;
395
396 QVariantMap parameters;
397 QNetworkRequest request(d->accessTokenUrl);
398 QUrlQuery query;
399 parameters.insert(akey: Key::grantType, QStringLiteral("authorization_code"));
400 parameters.insert(akey: Key::code, avalue: QUrl::toPercentEncoding(code));
401 parameters.insert(akey: Key::redirectUri, avalue: QUrl::toPercentEncoding(callback()));
402 parameters.insert(akey: Key::clientIdentifier, avalue: QUrl::toPercentEncoding(d->clientIdentifier));
403 if (!d->clientIdentifierSharedKey.isEmpty())
404 parameters.insert(akey: Key::clientSharedSecret, avalue: d->clientIdentifierSharedKey);
405 if (d->modifyParametersFunction)
406 d->modifyParametersFunction(Stage::RequestingAccessToken, &parameters);
407 query = QAbstractOAuthPrivate::createQuery(parameters);
408 request.setHeader(header: QNetworkRequest::ContentTypeHeader,
409 QStringLiteral("application/x-www-form-urlencoded"));
410
411 const QString data = query.toString(encoding: QUrl::FullyEncoded);
412 QNetworkReply *reply = d->networkAccessManager()->post(request, data: data.toUtf8());
413 d->currentReply = reply;
414 QAbstractOAuthReplyHandler *handler = replyHandler();
415 QObject::connect(sender: reply, signal: &QNetworkReply::finished,
416 slot: [handler, reply] { handler->networkReplyFinished(reply); });
417 QObjectPrivate::connect(sender: d->replyHandler.data(), signal: &QAbstractOAuthReplyHandler::tokensReceived, receiverPrivate: d,
418 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished,
419 type: Qt::UniqueConnection);
420 QObjectPrivate::connect(sender: d->networkAccessManager(),
421 signal: &QNetworkAccessManager::authenticationRequired,
422 receiverPrivate: d, slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate,
423 type: Qt::UniqueConnection);
424}
425
426/*!
427 Builds an authentication URL using \a url and \a parameters. This
428 function emits an authorizeWithBrowser() signal to require user
429 interaction.
430*/
431void QOAuth2AuthorizationCodeFlow::resourceOwnerAuthorization(const QUrl &url,
432 const QVariantMap &parameters)
433{
434 Q_D(QOAuth2AuthorizationCodeFlow);
435 if (Q_UNLIKELY(url != d->authorizationUrl)) {
436 qCWarning(d->loggingCategory, "Invalid URL: %s", qPrintable(url.toString()));
437 return;
438 }
439 const QUrl u = buildAuthenticateUrl(parameters);
440 QObjectPrivate::connect(sender: this, signal: &QOAuth2AuthorizationCodeFlow::authorizationCallbackReceived, receiverPrivate: d,
441 slot: &QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback,
442 type: Qt::UniqueConnection);
443 Q_EMIT authorizeWithBrowser(url: u);
444}
445
446QT_END_NAMESPACE
447
448#endif // QT_NO_HTTP
449

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