1 | // Copyright (C) 2017 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qoauth1signature.h" |
5 | #include "qoauth1signature_p.h" |
6 | |
7 | #include <QtCore/qurlquery.h> |
8 | #include <QtCore/qloggingcategory.h> |
9 | #include <QtCore/qmessageauthenticationcode.h> |
10 | |
11 | #include <QtNetwork/qnetworkaccessmanager.h> |
12 | |
13 | #include <functional> |
14 | #include <type_traits> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_LOGGING_CATEGORY(loggingCategory, "qt.networkauth.oauth1.signature" ) |
19 | |
20 | /*! |
21 | \class QOAuth1Signature |
22 | \inmodule QtNetworkAuth |
23 | \ingroup oauth |
24 | \brief Implements OAuth 1 signature methods. |
25 | \since 5.8 |
26 | |
27 | OAuth-authenticated requests can have two sets of credentials: |
28 | those passed via the "oauth_consumer_key" parameter and those in |
29 | the "oauth_token" parameter. In order for the server to verify |
30 | the authenticity of the request and prevent unauthorized access, |
31 | the client needs to prove that it is the rightful owner of the |
32 | credentials. This is accomplished using the shared-secret (or |
33 | RSA key) part of each set of credentials. |
34 | |
35 | OAuth specifies three methods for the client to establish its |
36 | rightful ownership of the credentials: "HMAC-SHA1", "RSA-SHA1", |
37 | and "PLAINTEXT". Each generates a "signature" with which the |
38 | request is "signed"; the first two use a digest of the data |
39 | signed in generating this, though the last does not. The |
40 | "RSA-SHA1" method is not supported here; it would use an RSA key |
41 | rather than the shared-secret associated with the client |
42 | credentials. |
43 | */ |
44 | |
45 | /*! |
46 | \enum QOAuth1Signature::HttpRequestMethod |
47 | |
48 | Indicates the HTTP request method. |
49 | |
50 | \value Head HEAD method. |
51 | \value Get GET method. |
52 | \value Put PUT method. |
53 | \value Post POST method. |
54 | \value Delete DELETE method. |
55 | \value Custom Identifies a custom method. |
56 | \value Unknown Method not set. |
57 | */ |
58 | |
59 | static_assert(static_cast<int>(QOAuth1Signature::HttpRequestMethod::Head) == |
60 | static_cast<int>(QNetworkAccessManager::HeadOperation) && |
61 | static_cast<int>(QOAuth1Signature::HttpRequestMethod::Get) == |
62 | static_cast<int>(QNetworkAccessManager::GetOperation) && |
63 | static_cast<int>(QOAuth1Signature::HttpRequestMethod::Put) == |
64 | static_cast<int>(QNetworkAccessManager::PutOperation) && |
65 | static_cast<int>(QOAuth1Signature::HttpRequestMethod::Post) == |
66 | static_cast<int>(QNetworkAccessManager::PostOperation) && |
67 | static_cast<int>(QOAuth1Signature::HttpRequestMethod::Delete) == |
68 | static_cast<int>(QNetworkAccessManager::DeleteOperation), |
69 | "Invalid QOAuth1Signature::HttpRequestMethod enumeration values" ); |
70 | |
71 | QOAuth1SignaturePrivate QOAuth1SignaturePrivate::shared_null; |
72 | |
73 | QOAuth1SignaturePrivate::QOAuth1SignaturePrivate(const QUrl &url, |
74 | QOAuth1Signature::HttpRequestMethod method, |
75 | const QMultiMap<QString, QVariant> ¶meters, |
76 | const QString &clientSharedKey, |
77 | const QString &tokenSecret) : |
78 | method(method), url(url), clientSharedKey(clientSharedKey), tokenSecret(tokenSecret), |
79 | parameters(parameters) |
80 | {} |
81 | |
82 | QByteArray QOAuth1SignaturePrivate::signatureBaseString() const |
83 | { |
84 | // https://tools.ietf.org/html/rfc5849#section-3.4.1 |
85 | QByteArray base; |
86 | |
87 | switch (method) { |
88 | case QOAuth1Signature::HttpRequestMethod::Head: |
89 | base.append(s: "HEAD" ); |
90 | break; |
91 | case QOAuth1Signature::HttpRequestMethod::Get: |
92 | base.append(s: "GET" ); |
93 | break; |
94 | case QOAuth1Signature::HttpRequestMethod::Put: |
95 | base.append(s: "PUT" ); |
96 | break; |
97 | case QOAuth1Signature::HttpRequestMethod::Post: |
98 | base.append(s: "POST" ); |
99 | break; |
100 | case QOAuth1Signature::HttpRequestMethod::Delete: |
101 | base.append(s: "DELETE" ); |
102 | break; |
103 | case QOAuth1Signature::HttpRequestMethod::Custom: |
104 | if (!customVerb.isEmpty()) { |
105 | base.append(a: customVerb); |
106 | } else { |
107 | qCCritical(loggingCategory, "QOAuth1Signature: HttpRequestMethod::Custom requires " |
108 | "the verb to be set via setCustomMethodString" ); |
109 | } |
110 | break; |
111 | default: |
112 | qCCritical(loggingCategory, "QOAuth1Signature: HttpRequestMethod not supported" ); |
113 | } |
114 | base.append(c: '&'); |
115 | base.append(a: QUrl::toPercentEncoding(url.toString(options: QUrl::RemoveQuery)) + "&" ); |
116 | |
117 | QMultiMap<QString, QVariant> p = parameters; |
118 | { |
119 | // replace '+' with spaces now before decoding so that '%2B' gets left as '+' |
120 | const QString query = url.query().replace(before: QLatin1Char('+'), after: QLatin1Char(' ')); |
121 | const auto queryItems = QUrlQuery(query).queryItems(encoding: QUrl::FullyDecoded); |
122 | for (auto it = queryItems.begin(), end = queryItems.end(); it != end; ++it) |
123 | p.insert(key: it->first, value: it->second); |
124 | } |
125 | base.append(a: encodeHeaders(headers: p)); |
126 | return base; |
127 | } |
128 | |
129 | QByteArray QOAuth1SignaturePrivate::secret() const |
130 | { |
131 | QByteArray secret; |
132 | secret.append(a: QUrl::toPercentEncoding(clientSharedKey)); |
133 | secret.append(c: '&'); |
134 | secret.append(a: QUrl::toPercentEncoding(tokenSecret)); |
135 | return secret; |
136 | } |
137 | |
138 | QByteArray QOAuth1SignaturePrivate::parameterString(const QMultiMap<QString, QVariant> ¶meters) |
139 | { |
140 | QByteArray ret; |
141 | auto previous = parameters.end(); |
142 | for (auto it = parameters.begin(), end = parameters.end(); it != end; previous = it++) { |
143 | if (previous != parameters.end()) { |
144 | if (Q_UNLIKELY(previous.key() == it.key())) |
145 | qCWarning(loggingCategory, "duplicated key %s" , qPrintable(it.key())); |
146 | ret.append(s: "&" ); |
147 | } |
148 | ret.append(a: QUrl::toPercentEncoding(it.key())); |
149 | ret.append(s: "=" ); |
150 | ret.append(a: QUrl::toPercentEncoding(it.value().toString())); |
151 | } |
152 | return ret; |
153 | } |
154 | |
155 | QByteArray QOAuth1SignaturePrivate::(const QMultiMap<QString, QVariant> &) |
156 | { |
157 | return QUrl::toPercentEncoding(QString::fromLatin1(ba: parameterString(parameters: headers))); |
158 | } |
159 | |
160 | /*! |
161 | Creates a QOAuth1Signature using |
162 | \list |
163 | \li \a url as the target address |
164 | \li \a method as the HTTP method used to send the request |
165 | \li and the given user \a parameters to augment the request. |
166 | \endlist |
167 | */ |
168 | QOAuth1Signature::QOAuth1Signature(const QUrl &url, QOAuth1Signature::HttpRequestMethod method, |
169 | const QMultiMap<QString, QVariant> ¶meters) : |
170 | d(new QOAuth1SignaturePrivate(url, method, parameters)) |
171 | {} |
172 | |
173 | /*! |
174 | Creates a QOAuth1Signature using |
175 | \list |
176 | \li \a url as the target address |
177 | \li \a clientSharedKey as the user token used to verify the |
178 | signature |
179 | \li \a tokenSecret as the negotiated token used to verify |
180 | the signature |
181 | \li \a method as the HTTP method used to send the request |
182 | \li and the given user \a parameters to augment the request. |
183 | \endlist |
184 | */ |
185 | QOAuth1Signature::QOAuth1Signature(const QUrl &url, const QString &clientSharedKey, |
186 | const QString &tokenSecret, HttpRequestMethod method, |
187 | const QMultiMap<QString, QVariant> ¶meters) : |
188 | d(new QOAuth1SignaturePrivate(url, method, parameters, clientSharedKey, tokenSecret)) |
189 | {} |
190 | |
191 | /*! |
192 | Creates a copy of \a other. |
193 | */ |
194 | QOAuth1Signature::QOAuth1Signature(const QOAuth1Signature &other) : d(other.d) |
195 | {} |
196 | |
197 | /*! |
198 | Move-constructs a QOAuth1Signature instance, taking over the |
199 | private data \a other was using. |
200 | */ |
201 | QOAuth1Signature::QOAuth1Signature(QOAuth1Signature &&other) : d(std::move(other.d)) |
202 | { |
203 | } |
204 | |
205 | /*! |
206 | Destroys the QOAuth1Signature. |
207 | */ |
208 | QOAuth1Signature::~QOAuth1Signature() |
209 | {} |
210 | |
211 | /*! |
212 | Returns the request method. |
213 | */ |
214 | QOAuth1Signature::HttpRequestMethod QOAuth1Signature::httpRequestMethod() const |
215 | { |
216 | return d->method; |
217 | } |
218 | |
219 | /*! |
220 | Sets the request \a method. |
221 | */ |
222 | void QOAuth1Signature::setHttpRequestMethod(QOAuth1Signature::HttpRequestMethod method) |
223 | { |
224 | d->method = method; |
225 | } |
226 | |
227 | /*! |
228 | \since 5.13 |
229 | |
230 | Returns the custom method string. |
231 | |
232 | \sa httpRequestMethod() |
233 | */ |
234 | QByteArray QOAuth1Signature::customMethodString() const |
235 | { |
236 | return d->customVerb; |
237 | } |
238 | |
239 | /*! |
240 | \since 5.13 |
241 | |
242 | Sets a custom request method. Will set the httpRequestMethod |
243 | to QOAuth1Signature::HttpRequestMethod::Custom and store the |
244 | \a verb to use it for the generation of the signature. |
245 | |
246 | \note Using this method is required when working with custom verbs. |
247 | Setting only the request method will fail, as the signure needs to |
248 | know the actual verb. |
249 | |
250 | \sa setHttpRequestMethod(), HttpRequestMethod |
251 | */ |
252 | void QOAuth1Signature::setCustomMethodString(const QByteArray &verb) |
253 | { |
254 | d->method = QOAuth1Signature::HttpRequestMethod::Custom; |
255 | d->customVerb = verb; |
256 | } |
257 | |
258 | /*! |
259 | Returns the URL. |
260 | */ |
261 | QUrl QOAuth1Signature::url() const |
262 | { |
263 | return d->url; |
264 | } |
265 | |
266 | /*! |
267 | Sets the URL to \a url. |
268 | */ |
269 | void QOAuth1Signature::setUrl(const QUrl &url) |
270 | { |
271 | d->url = url; |
272 | } |
273 | |
274 | /*! |
275 | Returns the parameters. |
276 | */ |
277 | QMultiMap<QString, QVariant> QOAuth1Signature::parameters() const |
278 | { |
279 | return d->parameters; |
280 | } |
281 | |
282 | /*! |
283 | Sets the \a parameters. |
284 | */ |
285 | void QOAuth1Signature::setParameters(const QMultiMap<QString, QVariant> ¶meters) |
286 | { |
287 | d->parameters.clear(); |
288 | for (auto it = parameters.cbegin(), end = parameters.cend(); it != end; ++it) |
289 | d->parameters.insert(key: it.key(), value: it.value()); |
290 | } |
291 | |
292 | /*! |
293 | Adds the request \a body to the signature. When a POST request |
294 | body contains arguments they should be included in the signed |
295 | data. |
296 | */ |
297 | void QOAuth1Signature::addRequestBody(const QUrlQuery &body) |
298 | { |
299 | const auto list = body.queryItems(); |
300 | for (auto it = list.begin(), end = list.end(); it != end; ++it) |
301 | d->parameters.replace(key: it->first, value: it->second); |
302 | } |
303 | |
304 | /*! |
305 | Inserts a new pair \a key, \a value into the signature. When a |
306 | POST request body contains arguments they should be included in |
307 | the signed data. |
308 | */ |
309 | void QOAuth1Signature::insert(const QString &key, const QVariant &value) |
310 | { |
311 | d->parameters.replace(key, value); |
312 | } |
313 | |
314 | /*! |
315 | Retrieves the list of keys of parameters included in the signed |
316 | data. |
317 | */ |
318 | QList<QString> QOAuth1Signature::keys() const |
319 | { |
320 | return d->parameters.uniqueKeys(); |
321 | } |
322 | |
323 | /*! |
324 | Removes \a key and any associated value from the signed data. |
325 | */ |
326 | QVariant QOAuth1Signature::take(const QString &key) |
327 | { |
328 | return d->parameters.take(key); |
329 | } |
330 | |
331 | /*! |
332 | Returns the value associated with \a key, if present in the |
333 | signed data, otherwise \a defaultValue. |
334 | */ |
335 | QVariant QOAuth1Signature::value(const QString &key, const QVariant &defaultValue) const |
336 | { |
337 | return d->parameters.value(key, defaultValue); |
338 | } |
339 | |
340 | /*! |
341 | Returns the user secret used to generate the signature. |
342 | */ |
343 | QString QOAuth1Signature::clientSharedKey() const |
344 | { |
345 | return d->clientSharedKey; |
346 | } |
347 | |
348 | /*! |
349 | Sets \a secret as the user secret used to generate the signature. |
350 | */ |
351 | void QOAuth1Signature::setClientSharedKey(const QString &secret) |
352 | { |
353 | d->clientSharedKey = secret; |
354 | } |
355 | |
356 | /*! |
357 | Returns the negotiated secret used to generate the signature. |
358 | */ |
359 | QString QOAuth1Signature::tokenSecret() const |
360 | { |
361 | return d->tokenSecret; |
362 | } |
363 | |
364 | /*! |
365 | Sets \a secret as the negotiated secret used to generate the |
366 | signature. |
367 | */ |
368 | void QOAuth1Signature::setTokenSecret(const QString &secret) |
369 | { |
370 | d->tokenSecret = secret; |
371 | } |
372 | |
373 | /*! |
374 | Generates the HMAC-SHA1 signature using the client shared secret |
375 | and, where available, token secret. |
376 | */ |
377 | QByteArray QOAuth1Signature::hmacSha1() const |
378 | { |
379 | return QMessageAuthenticationCode::hash(message: d->signatureBaseString(), key: d->secret(), |
380 | method: QCryptographicHash::Sha1); |
381 | } |
382 | |
383 | /*! |
384 | Generates the RSA-SHA1 signature. |
385 | |
386 | \note Currently this method is not supported. |
387 | */ |
388 | QByteArray QOAuth1Signature::rsaSha1() const |
389 | { |
390 | qCCritical(loggingCategory, "RSA-SHA1 signing method not supported" ); |
391 | return QByteArray(); |
392 | } |
393 | |
394 | /*! |
395 | Generates the PLAINTEXT signature. |
396 | */ |
397 | QByteArray QOAuth1Signature::plainText() const |
398 | { |
399 | return plainText(clientSharedSecret: d->clientSharedKey, tokenSecret: d->tokenSecret); |
400 | } |
401 | |
402 | /*! |
403 | Generates a PLAINTEXT signature from the client secret |
404 | \a clientSharedKey and the token secret \a tokenSecret. |
405 | */ |
406 | QByteArray QOAuth1Signature::plainText(const QString &clientSharedKey, |
407 | const QString &tokenSecret) |
408 | { |
409 | QByteArray ret; |
410 | ret += clientSharedKey.toUtf8() + '&' + tokenSecret.toUtf8(); |
411 | return ret; |
412 | } |
413 | |
414 | /*! |
415 | Swaps signature \a other with this signature. This operation is |
416 | very fast and never fails. |
417 | */ |
418 | void QOAuth1Signature::swap(QOAuth1Signature &other) |
419 | { |
420 | qSwap(value1&: d, value2&: other.d); |
421 | } |
422 | |
423 | QOAuth1Signature &QOAuth1Signature::operator=(const QOAuth1Signature &other) |
424 | { |
425 | if (d != other.d) { |
426 | QOAuth1Signature tmp(other); |
427 | tmp.swap(other&: *this); |
428 | } |
429 | return *this; |
430 | } |
431 | |
432 | /*! |
433 | Move-assignment operator. |
434 | */ |
435 | QOAuth1Signature &QOAuth1Signature::operator=(QOAuth1Signature &&other) |
436 | { |
437 | QOAuth1Signature moved(std::move(other)); |
438 | swap(other&: moved); |
439 | return *this; |
440 | } |
441 | |
442 | QT_END_NAMESPACE |
443 | |