2 * KQOAuth - An OAuth authentication library for Qt.
4 * Author: Johan Paul (johan.paul@d-pointer.com)
5 * http://www.d-pointer.com
7 * KQOAuth is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * KQOAuth is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with KQOAuth. If not, see <http://www.gnu.org/licenses/>.
22 #include <QCryptographicHash>
24 #include <QStringList>
27 #include <QtAlgorithms>
29 #include "kqoauthrequest.h"
30 #include "kqoauthrequest_p.h"
31 #include "kqoauthutils.h"
32 #include "kqoauthglobals.h"
35 //////////// Private d_ptr implementation /////////
37 KQOAuthRequestPrivate::KQOAuthRequestPrivate() :
43 KQOAuthRequestPrivate::~KQOAuthRequestPrivate()
48 // This method will not include the "oauthSignature" paramater, since it is calculated from these parameters.
49 void KQOAuthRequestPrivate::prepareRequest() {
51 // If parameter list is not empty, we don't want to insert these values by
52 // accident a second time. So giving up.
53 if( !requestParameters.isEmpty() ) {
57 switch ( requestType ) {
58 case KQOAuthRequest::TemporaryCredentials:
59 requestParameters.append( qMakePair( OAUTH_KEY_CALLBACK, oauthCallbackUrl.toString()) ); // This is so ugly that it is almost beautiful.
60 requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod) );
61 requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey ));
62 requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion ));
63 requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() ));
64 requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() ));
67 case KQOAuthRequest::AccessToken:
68 requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod ));
69 requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey ));
70 requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion ));
71 requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() ));
72 requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() ));
73 requestParameters.append( qMakePair( OAUTH_KEY_VERIFIER, oauthVerifier ));
74 requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken ));
77 case KQOAuthRequest::AuthorizedRequest:
78 requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod ));
79 requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey ));
80 requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion ));
81 requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() ));
82 requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() ));
83 requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken ));
91 void KQOAuthRequestPrivate::signRequest() {
92 QString signature = this->oauthSignature();
93 requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE, signature) );
96 QString KQOAuthRequestPrivate::oauthSignature() {
98 * http://oauth.net/core/1.0/#anchor16
99 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] where the
100 * Signature Base String is the text and the key is the concatenated values (each first encoded per Parameter
101 * Encoding) of the Consumer Secret and Token Secret, separated by an ‘&’ character (ASCII code 38) even if empty.
103 QByteArray baseString = this->requestBaseString();
105 QString secret = QString(QUrl::toPercentEncoding(oauthConsumerSecretKey)) + "&" + QString(QUrl::toPercentEncoding(oauthTokenSecret));
106 QString signature = KQOAuthUtils::hmac_sha1(baseString, secret);
109 qDebug() << "========== KQOAuthRequest has the following signature:";
110 qDebug() << " * Signature : " << QUrl::toPercentEncoding(signature) << "\n";
112 return QString( QUrl::toPercentEncoding(signature) );
115 bool normalizedParameterSort(const QPair<QString, QString> &left, const QPair<QString, QString> &right) {
116 QString keyLeft = left.first;
117 QString valueLeft = left.second;
118 QString keyRight = right.first;
119 QString valueRight = right.second;
121 if(keyLeft == keyRight) {
122 return (valueLeft < valueRight);
124 return (keyLeft < keyRight);
127 QByteArray KQOAuthRequestPrivate::requestBaseString() {
128 QByteArray baseString;
130 // Every request has these as the commont parameters.
131 baseString.append( oauthHttpMethodString.toUtf8() + "&"); // HTTP method
132 baseString.append( QUrl::toPercentEncoding( oauthRequestEndpoint.toString(QUrl::RemoveQuery) ) + "&" ); // The path and query components
134 QList< QPair<QString, QString> > baseStringParameters;
135 baseStringParameters.append(requestParameters);
136 baseStringParameters.append(additionalParameters);
138 // Sort the request parameters. These parameters have been
139 // initialized earlier.
140 qSort(baseStringParameters.begin(),
141 baseStringParameters.end(),
142 normalizedParameterSort
145 // Last append the request parameters correctly encoded.
146 baseString.append( encodedParamaterList(baseStringParameters) );
149 qDebug() << "========== KQOAuthRequest has the following base string:";
150 qDebug() << baseString << "\n";
156 QByteArray KQOAuthRequestPrivate::encodedParamaterList(const QList< QPair<QString, QString> > ¶meters) {
157 QByteArray resultList;
160 QPair<QString, QString> parameter;
162 // Do the debug output.
164 qDebug() << "========== KQOAuthRequest has the following parameters:";
166 foreach (parameter, parameters) {
168 resultList.append( "&" );
173 // Here we don't need to explicitely encode the strings to UTF-8 since
174 // QUrl::toPercentEncoding() takes care of that for us.
175 resultList.append( QUrl::toPercentEncoding(parameter.first) // Parameter key
177 + QUrl::toPercentEncoding(parameter.second) // Parameter value
190 return QUrl::toPercentEncoding(resultList);
193 QString KQOAuthRequestPrivate::oauthTimestamp() const {
194 // This is basically for unit tests only. In most cases we don't set the nonce beforehand.
195 if (!oauthTimestamp_.isEmpty()) {
196 return oauthTimestamp_;
199 #if QT_VERSION >= 0x040700
200 return QString::number(QDateTime::currentDateTimeUtc().toTime_t());
202 return QString::number(QDateTime::currentDateTime().toUTC().toTime_t());
207 QString KQOAuthRequestPrivate::oauthNonce() const {
208 // This is basically for unit tests only. In most cases we don't set the nonce beforehand.
209 if (!oauthNonce_.isEmpty()) {
213 return QString::number(qrand());
216 bool KQOAuthRequestPrivate::validateRequest() const {
217 switch ( requestType ) {
218 case KQOAuthRequest::TemporaryCredentials:
219 if (oauthRequestEndpoint.isEmpty()
220 || oauthConsumerKey.isEmpty()
221 || oauthNonce_.isEmpty()
222 || oauthSignatureMethod.isEmpty()
223 || oauthTimestamp_.isEmpty()
224 || oauthVersion.isEmpty())
230 case KQOAuthRequest::AccessToken:
231 if (oauthRequestEndpoint.isEmpty()
232 || oauthVerifier.isEmpty()
233 || oauthConsumerKey.isEmpty()
234 || oauthNonce_.isEmpty()
235 || oauthSignatureMethod.isEmpty()
236 || oauthTimestamp_.isEmpty()
237 || oauthToken.isEmpty()
238 || oauthTokenSecret.isEmpty()
239 || oauthVersion.isEmpty())
245 case KQOAuthRequest::AuthorizedRequest:
246 if (oauthRequestEndpoint.isEmpty()
247 || oauthConsumerKey.isEmpty()
248 || oauthNonce_.isEmpty()
249 || oauthSignatureMethod.isEmpty()
250 || oauthTimestamp_.isEmpty()
251 || oauthToken.isEmpty()
252 || oauthTokenSecret.isEmpty()
253 || oauthVersion.isEmpty())
263 // We should not come here.
267 //////////// Public implementation ////////////////
269 KQOAuthRequest::KQOAuthRequest(QObject *parent) :
271 d_ptr(new KQOAuthRequestPrivate)
273 d_ptr->debugOutput = false; // No debug output by default.
274 qsrand(QTime::currentTime().msec()); // We need to seed the nonce random number with something.
275 // However, we cannot do this while generating the nonce since
276 // we might get the same seed. So initializing here should be fine.
279 KQOAuthRequest::~KQOAuthRequest()
284 void KQOAuthRequest::initRequest(KQOAuthRequest::RequestType type, const QUrl &requestEndpoint) {
287 if (!requestEndpoint.isValid()) {
288 qWarning() << "Endpoint URL is not valid. Ignoring. This request might not work.";
292 if (type < 0 || type > KQOAuthRequest::AuthorizedRequest) {
293 qWarning() << "Invalid request type. Ignoring. This request might not work.";
300 // Set smart defaults.
301 d->requestType = type;
302 d->oauthRequestEndpoint = requestEndpoint;
303 d->oauthTimestamp_ = d->oauthTimestamp();
304 d->oauthNonce_ = d->oauthNonce();
305 this->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
306 this->setHttpMethod(KQOAuthRequest::POST);
307 d->oauthVersion = "1.0"; // Currently supports only version 1.0
309 d->contentType = "application/x-www-form-urlencoded";
312 void KQOAuthRequest::setConsumerKey(const QString &consumerKey) {
314 d->oauthConsumerKey = consumerKey;
317 void KQOAuthRequest::setConsumerSecretKey(const QString &consumerSecretKey) {
319 d->oauthConsumerSecretKey = consumerSecretKey;
322 void KQOAuthRequest::setCallbackUrl(const QUrl &callbackUrl) {
325 d->oauthCallbackUrl = callbackUrl;
328 void KQOAuthRequest::setSignatureMethod(KQOAuthRequest::RequestSignatureMethod requestMethod) {
330 QString requestMethodString;
332 switch (requestMethod) {
333 case KQOAuthRequest::PLAINTEXT:
334 requestMethodString = "PLAINTEXT";
336 case KQOAuthRequest::HMAC_SHA1:
337 requestMethodString = "HMAC-SHA1";
339 case KQOAuthRequest::RSA_SHA1:
340 requestMethodString = "RSA-SHA1";
343 // We should not come here
344 qWarning() << "Invalid signature method set.";
348 d->oauthSignatureMethod = requestMethodString;
351 void KQOAuthRequest::setTokenSecret(const QString &tokenSecret) {
354 d->oauthTokenSecret = tokenSecret;
357 void KQOAuthRequest::setToken(const QString &token) {
360 d->oauthToken = token;
363 void KQOAuthRequest::setVerifier(const QString &verifier) {
366 d->oauthVerifier = verifier;
370 void KQOAuthRequest::setHttpMethod(KQOAuthRequest::RequestHttpMethod httpMethod) {
373 QString requestHttpMethodString;
375 switch (httpMethod) {
376 case KQOAuthRequest::GET:
377 requestHttpMethodString = "GET";
379 case KQOAuthRequest::POST:
380 requestHttpMethodString = "POST";
383 qWarning() << "Invalid HTTP method set.";
387 d->oauthHttpMethod = httpMethod;
388 d->oauthHttpMethodString = requestHttpMethodString;
391 KQOAuthRequest::RequestHttpMethod KQOAuthRequest::httpMethod() const {
392 Q_D(const KQOAuthRequest);
394 return d->oauthHttpMethod;
397 void KQOAuthRequest::setAdditionalParameters(const KQOAuthParameters &additionalParams) {
400 QList<QString> additionalKeys = additionalParams.keys();
401 QList<QString> additionalValues = additionalParams.values();
404 foreach(QString key, additionalKeys) {
405 QString value = additionalValues.at(i);
406 d->additionalParameters.append( qMakePair(key, value) );
411 KQOAuthParameters KQOAuthRequest::additionalParameters() const {
412 Q_D(const KQOAuthRequest);
414 QMultiMap<QString, QString> additionalParams;
415 for(int i=0; i<d->additionalParameters.size(); i++) {
416 additionalParams.insert(d->additionalParameters.at(i).first,
417 d->additionalParameters.at(i).second);
420 return additionalParams;
423 KQOAuthRequest::RequestType KQOAuthRequest::requestType() const {
424 Q_D(const KQOAuthRequest);
425 return d->requestType;
428 QUrl KQOAuthRequest::requestEndpoint() const {
429 Q_D(const KQOAuthRequest);
430 return d->oauthRequestEndpoint;
433 QList<QByteArray> KQOAuthRequest::requestParameters() {
436 QList<QByteArray> requestParamList;
440 qWarning() << "Request is not valid! I will still sign it, but it will probably not work.";
444 QPair<QString, QString> requestParam;
447 foreach (requestParam, d->requestParameters) {
448 param = requestParam.first;
449 value = requestParam.second;
450 requestParamList.append(QString(param + "=\"" + value +"\"").toUtf8());
453 return requestParamList;
456 QString KQOAuthRequest::contentType()
458 Q_D(const KQOAuthRequest);
459 return d->contentType;
462 void KQOAuthRequest::setContentType(const QString &contentType)
465 d->contentType = contentType;
468 QByteArray KQOAuthRequest::rawData()
470 Q_D(const KQOAuthRequest);
471 return d->postRawData;
474 void KQOAuthRequest::setRawData(const QByteArray &rawData)
477 d->postRawData = rawData;
480 QByteArray KQOAuthRequest::requestBody() const {
481 Q_D(const KQOAuthRequest);
483 QByteArray postBodyContent;
485 for(int i=0; i < d->additionalParameters.size(); i++) {
487 postBodyContent.append("&");
492 QString key = d->additionalParameters.at(i).first;
493 QString value = d->additionalParameters.at(i).second;
495 postBodyContent.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() +
496 QUrl::toPercentEncoding(value));
498 return postBodyContent;
501 bool KQOAuthRequest::isValid() const {
502 Q_D(const KQOAuthRequest);
504 return d->validateRequest();
507 void KQOAuthRequest::setTimeout(int timeoutMilliseconds) {
509 d->timeout = timeoutMilliseconds;
512 void KQOAuthRequest::clearRequest() {
515 d->oauthRequestEndpoint = "";
516 d->oauthHttpMethodString = "";
517 d->oauthConsumerKey = "";
518 d->oauthConsumerSecretKey = "";
520 d->oauthTokenSecret = "";
521 d->oauthSignatureMethod = "";
522 d->oauthCallbackUrl = "";
523 d->oauthVerifier = "";
524 d->oauthTimestamp_ = "";
526 d->requestParameters.clear();
527 d->additionalParameters.clear();
531 void KQOAuthRequest::setEnableDebugOutput(bool enabled) {
533 d->debugOutput = enabled;
537 * Protected implementations for inherited classes
539 bool KQOAuthRequest::validateXAuthRequest() const {
540 Q_D(const KQOAuthRequest);
542 if (d->oauthRequestEndpoint.isEmpty()
543 || d->oauthConsumerKey.isEmpty()
544 || d->oauthNonce_.isEmpty()
545 || d->oauthSignatureMethod.isEmpty()
546 || d->oauthTimestamp_.isEmpty()
547 || d->oauthVersion.isEmpty())
556 * Private implementations for friend classes
558 QString KQOAuthRequest::consumerKeyForManager() const {
559 Q_D(const KQOAuthRequest);
560 return d->oauthConsumerKey;
563 QString KQOAuthRequest::consumerKeySecretForManager() const {
564 Q_D(const KQOAuthRequest);
565 return d->oauthConsumerSecretKey;
568 QUrl KQOAuthRequest::callbackUrlForManager() const {
569 Q_D(const KQOAuthRequest);
570 return d->oauthCallbackUrl;
573 void KQOAuthRequest::requestTimerStart()
576 if (d->timeout > 0) {
577 connect(&(d->timer), SIGNAL(timeout()), this, SIGNAL(requestTimedout()));
578 d->timer.start(d->timeout);
582 void KQOAuthRequest::requestTimerStop()
585 if (d->timeout > 0) {
586 disconnect(&(d->timer), SIGNAL(timeout()), this, SIGNAL(requestTimedout()));