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
16QT_BEGIN_NAMESPACE
17
18Q_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
59static_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
71QOAuth1SignaturePrivate QOAuth1SignaturePrivate::shared_null;
72
73QOAuth1SignaturePrivate::QOAuth1SignaturePrivate(const QUrl &url,
74 QOAuth1Signature::HttpRequestMethod method,
75 const QMultiMap<QString, QVariant> &parameters,
76 const QString &clientSharedKey,
77 const QString &tokenSecret) :
78 method(method), url(url), clientSharedKey(clientSharedKey), tokenSecret(tokenSecret),
79 parameters(parameters)
80{}
81
82QByteArray 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
129QByteArray 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
138QByteArray QOAuth1SignaturePrivate::parameterString(const QMultiMap<QString, QVariant> &parameters)
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
155QByteArray QOAuth1SignaturePrivate::encodeHeaders(const QMultiMap<QString, QVariant> &headers)
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*/
168QOAuth1Signature::QOAuth1Signature(const QUrl &url, QOAuth1Signature::HttpRequestMethod method,
169 const QMultiMap<QString, QVariant> &parameters) :
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*/
185QOAuth1Signature::QOAuth1Signature(const QUrl &url, const QString &clientSharedKey,
186 const QString &tokenSecret, HttpRequestMethod method,
187 const QMultiMap<QString, QVariant> &parameters) :
188 d(new QOAuth1SignaturePrivate(url, method, parameters, clientSharedKey, tokenSecret))
189{}
190
191/*!
192 Creates a copy of \a other.
193*/
194QOAuth1Signature::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*/
201QOAuth1Signature::QOAuth1Signature(QOAuth1Signature &&other) : d(std::move(other.d))
202{
203}
204
205/*!
206 Destroys the QOAuth1Signature.
207*/
208QOAuth1Signature::~QOAuth1Signature()
209{}
210
211/*!
212 Returns the request method.
213*/
214QOAuth1Signature::HttpRequestMethod QOAuth1Signature::httpRequestMethod() const
215{
216 return d->method;
217}
218
219/*!
220 Sets the request \a method.
221*/
222void 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*/
234QByteArray 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*/
252void QOAuth1Signature::setCustomMethodString(const QByteArray &verb)
253{
254 d->method = QOAuth1Signature::HttpRequestMethod::Custom;
255 d->customVerb = verb;
256}
257
258/*!
259 Returns the URL.
260*/
261QUrl QOAuth1Signature::url() const
262{
263 return d->url;
264}
265
266/*!
267 Sets the URL to \a url.
268*/
269void QOAuth1Signature::setUrl(const QUrl &url)
270{
271 d->url = url;
272}
273
274/*!
275 Returns the parameters.
276*/
277QMultiMap<QString, QVariant> QOAuth1Signature::parameters() const
278{
279 return d->parameters;
280}
281
282/*!
283 Sets the \a parameters.
284*/
285void QOAuth1Signature::setParameters(const QMultiMap<QString, QVariant> &parameters)
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*/
297void 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*/
309void 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*/
318QList<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*/
326QVariant 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*/
335QVariant 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*/
343QString 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*/
351void QOAuth1Signature::setClientSharedKey(const QString &secret)
352{
353 d->clientSharedKey = secret;
354}
355
356/*!
357 Returns the negotiated secret used to generate the signature.
358*/
359QString 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*/
368void 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*/
377QByteArray 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*/
388QByteArray 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*/
397QByteArray 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*/
406QByteArray 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*/
418void QOAuth1Signature::swap(QOAuth1Signature &other)
419{
420 qSwap(value1&: d, value2&: other.d);
421}
422
423QOAuth1Signature &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*/
435QOAuth1Signature &QOAuth1Signature::operator=(QOAuth1Signature &&other)
436{
437 QOAuth1Signature moved(std::move(other));
438 swap(other&: moved);
439 return *this;
440}
441
442QT_END_NAMESPACE
443

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