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

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