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
13QT_BEGIN_NAMESPACE
14
15Q_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
56static_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
68QOAuth1SignaturePrivate QOAuth1SignaturePrivate::shared_null;
69
70QOAuth1SignaturePrivate::QOAuth1SignaturePrivate(const QUrl &url,
71 QOAuth1Signature::HttpRequestMethod method,
72 const QMultiMap<QString, QVariant> &parameters,
73 const QString &clientSharedKey,
74 const QString &tokenSecret) :
75 method(method), url(url), clientSharedKey(clientSharedKey), tokenSecret(tokenSecret),
76 parameters(parameters)
77{}
78
79QByteArray 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
126QByteArray 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
135QByteArray QOAuth1SignaturePrivate::parameterString(const QMultiMap<QString, QVariant> &parameters)
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
152QByteArray QOAuth1SignaturePrivate::encodeHeaders(const QMultiMap<QString, QVariant> &headers)
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*/
165QOAuth1Signature::QOAuth1Signature(const QUrl &url, QOAuth1Signature::HttpRequestMethod method,
166 const QMultiMap<QString, QVariant> &parameters) :
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*/
182QOAuth1Signature::QOAuth1Signature(const QUrl &url, const QString &clientSharedKey,
183 const QString &tokenSecret, HttpRequestMethod method,
184 const QMultiMap<QString, QVariant> &parameters) :
185 d(new QOAuth1SignaturePrivate(url, method, parameters, clientSharedKey, tokenSecret))
186{}
187
188/*!
189 Creates a copy of \a other.
190*/
191QOAuth1Signature::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*/
198QOAuth1Signature::QOAuth1Signature(QOAuth1Signature &&other) : d(std::move(other.d))
199{
200}
201
202/*!
203 Destroys the QOAuth1Signature.
204*/
205QOAuth1Signature::~QOAuth1Signature()
206{}
207
208/*!
209 Returns the request method.
210*/
211QOAuth1Signature::HttpRequestMethod QOAuth1Signature::httpRequestMethod() const
212{
213 return d->method;
214}
215
216/*!
217 Sets the request \a method.
218*/
219void 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*/
231QByteArray 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*/
249void QOAuth1Signature::setCustomMethodString(const QByteArray &verb)
250{
251 d->method = QOAuth1Signature::HttpRequestMethod::Custom;
252 d->customVerb = verb;
253}
254
255/*!
256 Returns the URL.
257*/
258QUrl QOAuth1Signature::url() const
259{
260 return d->url;
261}
262
263/*!
264 Sets the URL to \a url.
265*/
266void QOAuth1Signature::setUrl(const QUrl &url)
267{
268 d->url = url;
269}
270
271/*!
272 Returns the parameters.
273*/
274QMultiMap<QString, QVariant> QOAuth1Signature::parameters() const
275{
276 return d->parameters;
277}
278
279/*!
280 Sets the \a parameters.
281*/
282void QOAuth1Signature::setParameters(const QMultiMap<QString, QVariant> &parameters)
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*/
294void 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*/
306void 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*/
315QList<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*/
323QVariant 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*/
332QVariant 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*/
340QString 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*/
348void QOAuth1Signature::setClientSharedKey(const QString &secret)
349{
350 d->clientSharedKey = secret;
351}
352
353/*!
354 Returns the negotiated secret used to generate the signature.
355*/
356QString 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*/
365void 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*/
374QByteArray 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*/
385QByteArray 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*/
394QByteArray 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*/
403QByteArray 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*/
415void QOAuth1Signature::swap(QOAuth1Signature &other)
416{
417 qSwap(value1&: d, value2&: other.d);
418}
419
420QOAuth1Signature &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*/
432QOAuth1Signature &QOAuth1Signature::operator=(QOAuth1Signature &&other)
433{
434 QOAuth1Signature moved(std::move(other));
435 swap(other&: moved);
436 return *this;
437}
438
439QT_END_NAMESPACE
440

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