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 | |
25 | QT_BEGIN_NAMESPACE |
26 | |
27 | using 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 | |
100 | using OAuth2 = QAbstractOAuth2Private::OAuth2KeyString; |
101 | const QString OAuth2::accessToken = u"access_token"_s ; |
102 | const QString OAuth2::apiKey = u"api_key"_s ; |
103 | const QString OAuth2::clientIdentifier = u"client_id"_s ; |
104 | const QString OAuth2::clientSharedSecret = u"client_secret"_s ; |
105 | const QString OAuth2::code = u"code"_s ; |
106 | const QString OAuth2::error = u"error"_s ; |
107 | const QString OAuth2::errorDescription = u"error_description"_s ; |
108 | const QString OAuth2::errorUri = u"error_uri"_s ; |
109 | const QString OAuth2::expiresIn = u"expires_in"_s ; |
110 | const QString OAuth2::grantType = u"grant_type"_s ; |
111 | const QString OAuth2::redirectUri = u"redirect_uri"_s ; |
112 | const QString OAuth2::refreshToken = u"refresh_token"_s ; |
113 | const QString OAuth2::responseType = u"response_type"_s ; |
114 | const QString OAuth2::scope = u"scope"_s ; |
115 | const QString OAuth2::state = u"state"_s ; |
116 | const QString OAuth2::tokenType = u"token_type"_s ; |
117 | |
118 | QAbstractOAuth2Private::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 | |
128 | QAbstractOAuth2Private::~QAbstractOAuth2Private() |
129 | {} |
130 | |
131 | QString QAbstractOAuth2Private::generateRandomState() |
132 | { |
133 | return QString::fromUtf8(ba: QAbstractOAuthPrivate::generateRandomString(length: 8)); |
134 | } |
135 | |
136 | QNetworkRequest 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 | */ |
159 | void 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 | */ |
173 | QAbstractOAuth2::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 | */ |
181 | QAbstractOAuth2::QAbstractOAuth2(QNetworkAccessManager *manager, QObject *parent) : |
182 | QAbstractOAuth(*new QAbstractOAuth2Private(qMakePair(value1: QString(), value2: QString()), |
183 | QUrl(), |
184 | manager), |
185 | parent) |
186 | {} |
187 | |
188 | QAbstractOAuth2::QAbstractOAuth2(QAbstractOAuth2Private &dd, QObject *parent) : |
189 | QAbstractOAuth(dd, parent) |
190 | {} |
191 | |
192 | void 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 | */ |
204 | QAbstractOAuth2::~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 | */ |
211 | QUrl QAbstractOAuth2::createAuthenticatedUrl(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
235 | QNetworkReply *QAbstractOAuth2::(const QUrl &url, const QVariantMap ¶meters) |
236 | { |
237 | Q_D(QAbstractOAuth2); |
238 | QNetworkReply *reply = d->networkAccessManager()->head(request: d->createRequest(url, parameters: ¶meters)); |
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 | */ |
251 | QNetworkReply *QAbstractOAuth2::get(const QUrl &url, const QVariantMap ¶meters) |
252 | { |
253 | Q_D(QAbstractOAuth2); |
254 | QNetworkReply *reply = d->networkAccessManager()->get(request: d->createRequest(url, parameters: ¶meters)); |
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 | */ |
267 | QNetworkReply *QAbstractOAuth2::post(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
286 | QNetworkReply *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 | */ |
306 | QNetworkReply *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 | */ |
322 | QNetworkReply *QAbstractOAuth2::put(const QUrl &url, const QVariantMap ¶meters) |
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 | */ |
341 | QNetworkReply *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 | */ |
361 | QNetworkReply *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 | */ |
377 | QNetworkReply *QAbstractOAuth2::deleteResource(const QUrl &url, const QVariantMap ¶meters) |
378 | { |
379 | Q_D(QAbstractOAuth2); |
380 | QNetworkReply *reply = d->networkAccessManager()->deleteResource( |
381 | request: d->createRequest(url, parameters: ¶meters)); |
382 | connect(sender: reply, signal: &QNetworkReply::finished, slot: [this, reply]() { emit finished(reply); }); |
383 | return reply; |
384 | } |
385 | |
386 | QString QAbstractOAuth2::scope() const |
387 | { |
388 | Q_D(const QAbstractOAuth2); |
389 | return d->scope; |
390 | } |
391 | |
392 | void 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 | |
401 | QString QAbstractOAuth2::userAgent() const |
402 | { |
403 | Q_D(const QAbstractOAuth2); |
404 | return d->userAgent; |
405 | } |
406 | |
407 | void 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 | */ |
420 | QString QAbstractOAuth2::responseType() const |
421 | { |
422 | Q_D(const QAbstractOAuth2); |
423 | return d->responseType; |
424 | } |
425 | |
426 | QString QAbstractOAuth2::clientIdentifierSharedKey() const |
427 | { |
428 | Q_D(const QAbstractOAuth2); |
429 | return d->clientIdentifierSharedKey; |
430 | } |
431 | |
432 | void 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 | |
441 | QString QAbstractOAuth2::state() const |
442 | { |
443 | Q_D(const QAbstractOAuth2); |
444 | return d->state; |
445 | } |
446 | |
447 | void 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 | |
456 | QDateTime 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 | */ |
471 | QString 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 | */ |
484 | void 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 | */ |
502 | QSslConfiguration 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 | */ |
516 | void 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 | |
537 | QT_END_NAMESPACE |
538 | |
539 | #include "moc_qabstractoauth2.cpp" |
540 | |
541 | #endif // QT_NO_HTTP |
542 | |