1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtNetwork/qtnetwork-config.h>
5
6#ifndef QT_NO_HTTP
7
8#include <qabstractoauth2.h>
9#include <private/qabstractoauth2_p.h>
10
11#include <QtCore/qurl.h>
12#include <QtCore/qurlquery.h>
13#include <QtCore/qbytearray.h>
14#include <QtCore/qmessageauthenticationcode.h>
15
16#include <QtNetwork/qnetworkreply.h>
17#include <QtNetwork/qnetworkrequest.h>
18#include <QtNetwork/qnetworkaccessmanager.h>
19#include <QtNetwork/qhttpmultipart.h>
20
21#ifndef QT_NO_SSL
22#include <QtNetwork/qsslconfiguration.h>
23#endif
24
25QT_BEGIN_NAMESPACE
26
27using namespace Qt::StringLiterals;
28
29/*!
30 \class QAbstractOAuth2
31 \inmodule QtNetworkAuth
32 \ingroup oauth
33 \brief The QAbstractOAuth2 class is the base of all
34 implementations of OAuth 2 authentication methods.
35 \since 5.8
36
37 The class defines the basic interface of the OAuth 2
38 authentication classes. By inheriting this class, you
39 can create custom authentication methods using the OAuth 2
40 standard for different web services.
41
42 A description of how OAuth 2 works can be found in:
43 \l {https://tools.ietf.org/html/rfc6749}{The OAuth 2.0
44 Authorization Framework}
45*/
46/*!
47 \property QAbstractOAuth2::scope
48 \brief This property holds the desired scope which defines the
49 permissions requested by the client.
50*/
51
52/*!
53 \property QAbstractOAuth2::userAgent
54 This property holds the User-Agent header used to create the
55 network requests.
56
57 The default value is "QtOAuth/1.0 (+https://www.qt.io)".
58*/
59
60/*!
61 \property QAbstractOAuth2::clientIdentifierSharedKey
62 This property holds the client shared key used as a password if
63 the server requires authentication to request the token.
64*/
65
66/*!
67 \property QAbstractOAuth2::state
68 This property holds the string sent to the server during
69 authentication. The state is used to identify and validate the
70 request when the callback is received.
71*/
72
73/*!
74 \property QAbstractOAuth2::expiration
75 This property holds the expiration time of the current access
76 token.
77*/
78
79/*!
80 \fn QAbstractOAuth2::error(const QString &error, const QString &errorDescription, const QUrl &uri)
81
82 Signal emitted when the server responds to the authorization request with
83 an error as defined in \l {https://www.rfc-editor.org/rfc/rfc6749#section-5.2}
84 {RFC 6749 error response}.
85
86 \a error is the name of the error; \a errorDescription describes the error
87 and \a uri is an optional URI containing more information about the error.
88
89 \sa QAbstractOAuth::requestFailed()
90*/
91
92/*!
93 \fn QAbstractOAuth2::authorizationCallbackReceived(const QVariantMap &data)
94
95 Signal emitted when the reply server receives the authorization
96 callback from the server: \a data contains the values received
97 from the server.
98*/
99
100using OAuth2 = QAbstractOAuth2Private::OAuth2KeyString;
101const QString OAuth2::accessToken = u"access_token"_s;
102const QString OAuth2::apiKey = u"api_key"_s;
103const QString OAuth2::clientIdentifier = u"client_id"_s;
104const QString OAuth2::clientSharedSecret = u"client_secret"_s;
105const QString OAuth2::code = u"code"_s;
106const QString OAuth2::error = u"error"_s;
107const QString OAuth2::errorDescription = u"error_description"_s;
108const QString OAuth2::errorUri = u"error_uri"_s;
109const QString OAuth2::expiresIn = u"expires_in"_s;
110const QString OAuth2::grantType = u"grant_type"_s;
111const QString OAuth2::redirectUri = u"redirect_uri"_s;
112const QString OAuth2::refreshToken = u"refresh_token"_s;
113const QString OAuth2::responseType = u"response_type"_s;
114const QString OAuth2::scope = u"scope"_s;
115const QString OAuth2::state = u"state"_s;
116const QString OAuth2::tokenType = u"token_type"_s;
117
118QAbstractOAuth2Private::QAbstractOAuth2Private(const QPair<QString, QString> &clientCredentials,
119 const QUrl &authorizationUrl,
120 QNetworkAccessManager *manager) :
121 QAbstractOAuthPrivate("qt.networkauth.oauth2",
122 authorizationUrl,
123 clientCredentials.first,
124 manager),
125 clientIdentifierSharedKey(clientCredentials.second)
126{}
127
128QAbstractOAuth2Private::~QAbstractOAuth2Private()
129{}
130
131QString QAbstractOAuth2Private::generateRandomState()
132{
133 return QString::fromUtf8(ba: QAbstractOAuthPrivate::generateRandomString(length: 8));
134}
135
136QNetworkRequest QAbstractOAuth2Private::createRequest(QUrl url, const QVariantMap *parameters)
137{
138 QUrlQuery query(url.query());
139
140 QNetworkRequest request;
141 if (parameters) {
142 for (auto it = parameters->begin(), end = parameters->end(); it != end; ++it)
143 query.addQueryItem(key: it.key(), value: it.value().toString());
144 url.setQuery(query);
145 } else { // POST, PUT request
146 addContentTypeHeaders(request: &request);
147 }
148
149 request.setUrl(url);
150 request.setHeader(header: QNetworkRequest::UserAgentHeader, value: userAgent);
151 const QString bearer = bearerFormat.arg(a: token);
152 request.setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
153 return request;
154}
155
156/*!
157 \reimp
158*/
159void QAbstractOAuth2::prepareRequest(QNetworkRequest *request, const QByteArray &verb,
160 const QByteArray &body)
161{
162 Q_D(QAbstractOAuth2);
163 Q_UNUSED(verb);
164 Q_UNUSED(body);
165 request->setHeader(header: QNetworkRequest::UserAgentHeader, value: d->userAgent);
166 const QString bearer = d->bearerFormat.arg(a: d->token);
167 request->setRawHeader(headerName: "Authorization", value: bearer.toUtf8());
168}
169
170/*!
171 Constructs a QAbstractOAuth2 object using \a parent as parent.
172*/
173QAbstractOAuth2::QAbstractOAuth2(QObject *parent) :
174 QAbstractOAuth2(nullptr, parent)
175{}
176
177/*!
178 Constructs a QAbstractOAuth2 object using \a parent as parent and
179 sets \a manager as the network access manager.
180*/
181QAbstractOAuth2::QAbstractOAuth2(QNetworkAccessManager *manager, QObject *parent) :
182 QAbstractOAuth(*new QAbstractOAuth2Private(qMakePair(value1: QString(), value2: QString()),
183 QUrl(),
184 manager),
185 parent)
186{}
187
188QAbstractOAuth2::QAbstractOAuth2(QAbstractOAuth2Private &dd, QObject *parent) :
189 QAbstractOAuth(dd, parent)
190{}
191
192void QAbstractOAuth2::setResponseType(const QString &responseType)
193{
194 Q_D(QAbstractOAuth2);
195 if (d->responseType != responseType) {
196 d->responseType = responseType;
197 Q_EMIT responseTypeChanged(responseType);
198 }
199}
200
201/*!
202 Destroys the QAbstractOAuth2 instance.
203*/
204QAbstractOAuth2::~QAbstractOAuth2()
205{}
206
207/*!
208 The returned URL is based on \a url, combining it with the given
209 \a parameters and the access token.
210*/
211QUrl QAbstractOAuth2::createAuthenticatedUrl(const QUrl &url, const QVariantMap &parameters)
212{
213 Q_D(const QAbstractOAuth2);
214 if (Q_UNLIKELY(d->token.isEmpty())) {
215 qCWarning(d->loggingCategory, "Empty access token");
216 return QUrl();
217 }
218 QUrl ret = url;
219 QUrlQuery query(ret.query());
220 query.addQueryItem(key: OAuth2::accessToken, value: d->token);
221 for (auto it = parameters.begin(), end = parameters.end(); it != end ;++it)
222 query.addQueryItem(key: it.key(), value: it.value().toString());
223 ret.setQuery(query);
224 return ret;
225}
226
227/*!
228 Sends an authenticated HEAD request and returns a new
229 QNetworkReply. The \a url and \a parameters are used to create
230 the request.
231
232 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.4}
233 {Hypertext Transfer Protocol -- HTTP/1.1: HEAD}
234*/
235QNetworkReply *QAbstractOAuth2::head(const QUrl &url, const QVariantMap &parameters)
236{
237 Q_D(QAbstractOAuth2);
238 QNetworkReply *reply = d->networkAccessManager()->head(request: d->createRequest(url, parameters: &parameters));
239 connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); });
240 return reply;
241}
242
243/*!
244 Sends an authenticated GET request and returns a new
245 QNetworkReply. The \a url and \a parameters are used to create
246 the request.
247
248 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.3}
249 {Hypertext Transfer Protocol -- HTTP/1.1: GET}
250*/
251QNetworkReply *QAbstractOAuth2::get(const QUrl &url, const QVariantMap &parameters)
252{
253 Q_D(QAbstractOAuth2);
254 QNetworkReply *reply = d->networkAccessManager()->get(request: d->createRequest(url, parameters: &parameters));
255 connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); });
256 return reply;
257}
258
259/*!
260 Sends an authenticated POST request and returns a new
261 QNetworkReply. The \a url and \a parameters are used to create
262 the request.
263
264 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.5}
265 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
266*/
267QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QVariantMap &parameters)
268{
269 Q_D(QAbstractOAuth2);
270 const auto data = d->convertParameters(parameters);
271 return post(url, data);
272}
273
274/*!
275 \since 5.10
276
277 \overload
278
279 Sends an authenticated POST request and returns a new
280 QNetworkReply. The \a url and \a data are used to create
281 the request.
282
283 \sa post(), {https://tools.ietf.org/html/rfc2616#section-9.6}
284 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
285*/
286QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QByteArray &data)
287{
288 Q_D(QAbstractOAuth2);
289 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), data);
290 connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); });
291 return reply;
292}
293
294/*!
295 \since 5.10
296
297 \overload
298
299 Sends an authenticated POST request and returns a new
300 QNetworkReply. The \a url and \a multiPart are used to create
301 the request.
302
303 \sa post(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
304 {Hypertext Transfer Protocol -- HTTP/1.1: POST}
305*/
306QNetworkReply *QAbstractOAuth2::post(const QUrl &url, QHttpMultiPart *multiPart)
307{
308 Q_D(QAbstractOAuth2);
309 QNetworkReply *reply = d->networkAccessManager()->post(request: d->createRequest(url), multiPart);
310 connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); });
311 return reply;
312}
313
314/*!
315 Sends an authenticated PUT request and returns a new
316 QNetworkReply. The \a url and \a parameters are used to create
317 the request.
318
319 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.6}
320 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
321*/
322QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QVariantMap &parameters)
323{
324 Q_D(QAbstractOAuth2);
325 const auto data = d->convertParameters(parameters);
326 return put(url, data);
327}
328
329/*!
330 \since 5.10
331
332 \overload
333
334 Sends an authenticated PUT request and returns a new
335 QNetworkReply. The \a url and \a data are used to create
336 the request.
337
338 \sa put(), {https://tools.ietf.org/html/rfc2616#section-9.6}
339 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
340*/
341QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QByteArray &data)
342{
343 Q_D(QAbstractOAuth2);
344 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), data);
345 connect(sender: reply, signal: &QNetworkReply::finished, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
346 return reply;
347}
348
349/*!
350 \since 5.10
351
352 \overload
353
354 Sends an authenticated PUT request and returns a new
355 QNetworkReply. The \a url and \a multiPart are used to create
356 the request.
357
358 \sa put(), QHttpMultiPart, {https://tools.ietf.org/html/rfc2616#section-9.6}
359 {Hypertext Transfer Protocol -- HTTP/1.1: PUT}
360*/
361QNetworkReply *QAbstractOAuth2::put(const QUrl &url, QHttpMultiPart *multiPart)
362{
363 Q_D(QAbstractOAuth2);
364 QNetworkReply *reply = d->networkAccessManager()->put(request: d->createRequest(url), multiPart);
365 connect(sender: reply, signal: &QNetworkReply::finished, slot: std::bind(f: &QAbstractOAuth::finished, args: this, args&: reply));
366 return reply;
367}
368
369/*!
370 Sends an authenticated DELETE request and returns a new
371 QNetworkReply. The \a url and \a parameters are used to create
372 the request.
373
374 \b {See also}: \l {https://tools.ietf.org/html/rfc2616#section-9.7}
375 {Hypertext Transfer Protocol -- HTTP/1.1: DELETE}
376*/
377QNetworkReply *QAbstractOAuth2::deleteResource(const QUrl &url, const QVariantMap &parameters)
378{
379 Q_D(QAbstractOAuth2);
380 QNetworkReply *reply = d->networkAccessManager()->deleteResource(
381 request: d->createRequest(url, parameters: &parameters));
382 connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); });
383 return reply;
384}
385
386QString QAbstractOAuth2::scope() const
387{
388 Q_D(const QAbstractOAuth2);
389 return d->scope;
390}
391
392void QAbstractOAuth2::setScope(const QString &scope)
393{
394 Q_D(QAbstractOAuth2);
395 if (d->scope != scope) {
396 d->scope = scope;
397 Q_EMIT scopeChanged(scope);
398 }
399}
400
401QString QAbstractOAuth2::userAgent() const
402{
403 Q_D(const QAbstractOAuth2);
404 return d->userAgent;
405}
406
407void QAbstractOAuth2::setUserAgent(const QString &userAgent)
408{
409 Q_D(QAbstractOAuth2);
410 if (d->userAgent != userAgent) {
411 d->userAgent = userAgent;
412 Q_EMIT userAgentChanged(userAgent);
413 }
414}
415
416/*!
417 Returns the \l {https://tools.ietf.org/html/rfc6749#section-3.1.1}
418 {response_type} used.
419*/
420QString QAbstractOAuth2::responseType() const
421{
422 Q_D(const QAbstractOAuth2);
423 return d->responseType;
424}
425
426QString QAbstractOAuth2::clientIdentifierSharedKey() const
427{
428 Q_D(const QAbstractOAuth2);
429 return d->clientIdentifierSharedKey;
430}
431
432void QAbstractOAuth2::setClientIdentifierSharedKey(const QString &clientIdentifierSharedKey)
433{
434 Q_D(QAbstractOAuth2);
435 if (d->clientIdentifierSharedKey != clientIdentifierSharedKey) {
436 d->clientIdentifierSharedKey = clientIdentifierSharedKey;
437 Q_EMIT clientIdentifierSharedKeyChanged(clientIdentifierSharedKey);
438 }
439}
440
441QString QAbstractOAuth2::state() const
442{
443 Q_D(const QAbstractOAuth2);
444 return d->state;
445}
446
447void QAbstractOAuth2::setState(const QString &state)
448{
449 Q_D(QAbstractOAuth2);
450 if (state != d->state) {
451 d->state = state;
452 Q_EMIT stateChanged(state);
453 }
454}
455
456QDateTime QAbstractOAuth2::expirationAt() const
457{
458 Q_D(const QAbstractOAuth2);
459 return d->expiresAt;
460}
461
462/*!
463 \brief Gets the current refresh token.
464
465 Refresh tokens usually have longer lifespans than access tokens,
466 so it makes sense to save them for later use.
467
468 Returns the current refresh token or an empty string, if
469 there is no refresh token available.
470*/
471QString QAbstractOAuth2::refreshToken() const
472{
473 Q_D(const QAbstractOAuth2);
474 return d->refreshToken;
475}
476
477/*!
478 \brief Sets the new refresh token \a refreshToken to be used.
479
480 A custom refresh token can be used to refresh the access token via this method and then
481 the access token can be refreshed via QOAuth2AuthorizationCodeFlow::refreshAccessToken().
482
483*/
484void QAbstractOAuth2::setRefreshToken(const QString &refreshToken)
485{
486 Q_D(QAbstractOAuth2);
487 if (d->refreshToken != refreshToken) {
488 d->refreshToken = refreshToken;
489 Q_EMIT refreshTokenChanged(refreshToken);
490 }
491}
492
493#ifndef QT_NO_SSL
494/*!
495 \since 6.5
496
497 Returns the TLS configuration to be used when establishing a mutual TLS
498 connection between the client and the Authorization Server.
499
500 \sa setSslConfiguration(), sslConfigurationChanged()
501*/
502QSslConfiguration QAbstractOAuth2::sslConfiguration() const
503{
504 Q_D(const QAbstractOAuth2);
505 return d->sslConfiguration.value_or(u: QSslConfiguration());
506}
507
508/*!
509 \since 6.5
510
511 Sets the TLS \a configuration to be used when establishing
512 a mutual TLS connection between the client and the Authorization Server.
513
514 \sa sslConfiguration(), sslConfigurationChanged()
515*/
516void QAbstractOAuth2::setSslConfiguration(const QSslConfiguration &configuration)
517{
518 Q_D(QAbstractOAuth2);
519 const bool configChanged = !d->sslConfiguration || (*d->sslConfiguration != configuration);
520 if (configChanged) {
521 d->sslConfiguration = configuration;
522 Q_EMIT sslConfigurationChanged(configuration);
523 }
524}
525
526/*!
527 \fn void QAbstractOAuth2::sslConfigurationChanged(const QSslConfiguration &configuration)
528 \since 6.5
529
530 The signal is emitted when the TLS configuration has changed.
531 The \a configuration parameter contains the new TLS configuration.
532
533 \sa sslConfiguration(), setSslConfiguration()
534*/
535#endif // !QT_NO_SSL
536
537QT_END_NAMESPACE
538
539#include "moc_qabstractoauth2.cpp"
540
541#endif // QT_NO_HTTP
542

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