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