/** * KQOAuth - An OAuth authentication library for Qt. * * Author: Johan Paul (johan.paul@d-pointer.com) * http://www.d-pointer.com * * KQOAuth is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KQOAuth is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with KQOAuth. If not, see . */ #include #include #include #include #include #include #include #include "kqoauthrequest.h" #include "kqoauthrequest_p.h" #include "kqoauthutils.h" #include "kqoauthglobals.h" //////////// Private d_ptr implementation ///////// KQOAuthRequestPrivate::KQOAuthRequestPrivate() : timeout(0) { } KQOAuthRequestPrivate::~KQOAuthRequestPrivate() { } // This method will not include the "oauthSignature" paramater, since it is calculated from these parameters. void KQOAuthRequestPrivate::prepareRequest() { // If parameter list is not empty, we don't want to insert these values by // accident a second time. So giving up. if( !requestParameters.isEmpty() ) { return; } switch ( requestType ) { case KQOAuthRequest::TemporaryCredentials: requestParameters.append( qMakePair( OAUTH_KEY_CALLBACK, oauthCallbackUrl.toString()) ); // This is so ugly that it is almost beautiful. requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod) ); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); break; case KQOAuthRequest::AccessToken: requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod )); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); requestParameters.append( qMakePair( OAUTH_KEY_VERIFIER, oauthVerifier )); requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken )); break; case KQOAuthRequest::AuthorizedRequest: requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE_METHOD, oauthSignatureMethod )); requestParameters.append( qMakePair( OAUTH_KEY_CONSUMER_KEY, oauthConsumerKey )); requestParameters.append( qMakePair( OAUTH_KEY_VERSION, oauthVersion )); requestParameters.append( qMakePair( OAUTH_KEY_TIMESTAMP, this->oauthTimestamp() )); requestParameters.append( qMakePair( OAUTH_KEY_NONCE, this->oauthNonce() )); requestParameters.append( qMakePair( OAUTH_KEY_TOKEN, oauthToken )); break; default: break; } } void KQOAuthRequestPrivate::signRequest() { QString signature = this->oauthSignature(); requestParameters.append( qMakePair( OAUTH_KEY_SIGNATURE, signature) ); } QString KQOAuthRequestPrivate::oauthSignature() { /** * http://oauth.net/core/1.0/#anchor16 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] where the * Signature Base String is the text and the key is the concatenated values (each first encoded per Parameter * Encoding) of the Consumer Secret and Token Secret, separated by an ‘&’ character (ASCII code 38) even if empty. **/ QByteArray baseString = this->requestBaseString(); QString secret = QString(QUrl::toPercentEncoding(oauthConsumerSecretKey)) + "&" + QString(QUrl::toPercentEncoding(oauthTokenSecret)); QString signature = KQOAuthUtils::hmac_sha1(baseString, secret); if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following signature:"; qDebug() << " * Signature : " << QUrl::toPercentEncoding(signature) << "\n"; } return QString( QUrl::toPercentEncoding(signature) ); } bool normalizedParameterSort(const QPair &left, const QPair &right) { QString keyLeft = left.first; QString valueLeft = left.second; QString keyRight = right.first; QString valueRight = right.second; if(keyLeft == keyRight) { return (valueLeft < valueRight); } else { return (keyLeft < keyRight); } } QByteArray KQOAuthRequestPrivate::requestBaseString() { QByteArray baseString; // Every request has these as the commont parameters. baseString.append( oauthHttpMethodString.toUtf8() + "&"); // HTTP method baseString.append( QUrl::toPercentEncoding( oauthRequestEndpoint.toString(QUrl::RemoveQuery) ) + "&" ); // The path and query components QList< QPair > baseStringParameters; baseStringParameters.append(requestParameters); baseStringParameters.append(additionalParameters); // Sort the request parameters. These parameters have been // initialized earlier. qSort(baseStringParameters.begin(), baseStringParameters.end(), normalizedParameterSort ); // Last append the request parameters correctly encoded. baseString.append( encodedParamaterList(baseStringParameters) ); if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following base string:"; qDebug() << baseString << "\n"; } return baseString; } QByteArray KQOAuthRequestPrivate::encodedParamaterList(const QList< QPair > ¶meters) { QByteArray resultList; bool first = true; QPair parameter; // Do the debug output. if (debugOutput) { qDebug() << "========== KQOAuthRequest has the following parameters:"; } foreach (parameter, parameters) { if(!first) { resultList.append( "&" ); } else { first = false; } // Here we don't need to explicitely encode the strings to UTF-8 since // QUrl::toPercentEncoding() takes care of that for us. resultList.append( QUrl::toPercentEncoding(parameter.first) // Parameter key + "=" + QUrl::toPercentEncoding(parameter.second) // Parameter value ); if (debugOutput) { qDebug() << " * " << parameter.first << " : " << parameter.second; } } if (debugOutput) { qDebug() << "\n"; } return QUrl::toPercentEncoding(resultList); } QString KQOAuthRequestPrivate::oauthTimestamp() const { // This is basically for unit tests only. In most cases we don't set the nonce beforehand. if (!oauthTimestamp_.isEmpty()) { return oauthTimestamp_; } #if QT_VERSION >= 0x040700 return QString::number(QDateTime::currentDateTimeUtc().toTime_t()); #else return QString::number(QDateTime::currentDateTime().toUTC().toTime_t()); #endif } QString KQOAuthRequestPrivate::oauthNonce() const { // This is basically for unit tests only. In most cases we don't set the nonce beforehand. return QString::number(qrand()); } bool KQOAuthRequestPrivate::validateRequest() const { switch ( requestType ) { case KQOAuthRequest::TemporaryCredentials: if (oauthRequestEndpoint.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; case KQOAuthRequest::AccessToken: if (oauthRequestEndpoint.isEmpty() || oauthVerifier.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthToken.isEmpty() || oauthTokenSecret.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; case KQOAuthRequest::AuthorizedRequest: if(requestOAuthMethod == KQOAuthRequest::OAUTH1) { if (oauthRequestEndpoint.isEmpty() || oauthConsumerKey.isEmpty() || oauthNonce_.isEmpty() || oauthSignatureMethod.isEmpty() || oauthTimestamp_.isEmpty() || oauthToken.isEmpty() || oauthTokenSecret.isEmpty() || oauthVersion.isEmpty()) { return false; } return true; } else { if(oauthToken.isEmpty()){ return false; } return true; } default: return false; } // We should not come here. return false; } //////////// Public implementation //////////////// KQOAuthRequest::KQOAuthRequest(QObject *parent) : QObject(parent), d_ptr(new KQOAuthRequestPrivate) { d_ptr->debugOutput = false; // No debug output by default. qsrand(QTime::currentTime().msec()); // We need to seed the nonce random number with something. // However, we cannot do this while generating the nonce since // we might get the same seed. So initializing here should be fine. } KQOAuthRequest::~KQOAuthRequest() { delete d_ptr; } void KQOAuthRequest::initRequest(KQOAuthRequest::RequestType type, const QUrl &requestEndpoint) { Q_D(KQOAuthRequest); if (!requestEndpoint.isValid()) { qWarning() << "Endpoint URL is not valid. Ignoring. This request might not work."; return; } if (type < 0 || type > KQOAuthRequest::AuthorizedRequest) { qWarning() << "Invalid request type. Ignoring. This request might not work."; return; } // Clear the request clearRequest(); // Set smart defaults. d->requestType = type; d->oauthRequestEndpoint = requestEndpoint; d->oauthTimestamp_ = d->oauthTimestamp(); d->oauthNonce_ = d->oauthNonce(); this->setSignatureMethod(KQOAuthRequest::HMAC_SHA1); this->setHttpMethod(KQOAuthRequest::POST); this->setRequestOAuthMethod(KQOAuthRequest::OAUTH1); d->oauthVersion = "1.0"; // Currently supports only version 1.0 d->contentType = "application/x-www-form-urlencoded"; } void KQOAuthRequest::setConsumerKey(const QString &consumerKey) { Q_D(KQOAuthRequest); d->oauthConsumerKey = consumerKey; } void KQOAuthRequest::setConsumerSecretKey(const QString &consumerSecretKey) { Q_D(KQOAuthRequest); d->oauthConsumerSecretKey = consumerSecretKey; } void KQOAuthRequest::setCallbackUrl(const QUrl &callbackUrl) { Q_D(KQOAuthRequest); d->oauthCallbackUrl = callbackUrl; } void KQOAuthRequest::setSignatureMethod(KQOAuthRequest::RequestSignatureMethod requestMethod) { Q_D(KQOAuthRequest); QString requestMethodString; switch (requestMethod) { case KQOAuthRequest::PLAINTEXT: requestMethodString = "PLAINTEXT"; break; case KQOAuthRequest::HMAC_SHA1: requestMethodString = "HMAC-SHA1"; break; case KQOAuthRequest::RSA_SHA1: requestMethodString = "RSA-SHA1"; break; default: // We should not come here qWarning() << "Invalid signature method set."; break; } d->oauthSignatureMethod = requestMethodString; } void KQOAuthRequest::setTokenSecret(const QString &tokenSecret) { Q_D(KQOAuthRequest); d->oauthTokenSecret = tokenSecret; } void KQOAuthRequest::setToken(const QString &token) { Q_D(KQOAuthRequest); d->oauthToken = token; } void KQOAuthRequest::setVerifier(const QString &verifier) { Q_D(KQOAuthRequest); d->oauthVerifier = verifier; } void KQOAuthRequest::setHttpMethod(KQOAuthRequest::RequestHttpMethod httpMethod) { Q_D(KQOAuthRequest); QString requestHttpMethodString; switch (httpMethod) { case KQOAuthRequest::GET: requestHttpMethodString = "GET"; break; case KQOAuthRequest::POST: requestHttpMethodString = "POST"; break; default: qWarning() << "Invalid HTTP method set."; break; } d->oauthHttpMethod = httpMethod; d->oauthHttpMethodString = requestHttpMethodString; } void KQOAuthRequest::setRequestOAuthMethod(KQOAuthRequest::RequestOAuthMethod oauthMethod) { Q_D(KQOAuthRequest); d->requestOAuthMethod = oauthMethod; } KQOAuthRequest::RequestHttpMethod KQOAuthRequest::httpMethod() const { Q_D(const KQOAuthRequest); return d->oauthHttpMethod; } KQOAuthRequest::RequestOAuthMethod KQOAuthRequest::oauthMethod() const { Q_D(const KQOAuthRequest); return d->requestOAuthMethod; } void KQOAuthRequest::setAdditionalParameters(const KQOAuthParameters &additionalParams) { Q_D(KQOAuthRequest); QList additionalKeys = additionalParams.keys(); QList additionalValues = additionalParams.values(); int i=0; foreach(QString key, additionalKeys) { QString value = additionalValues.at(i); d->additionalParameters.append( qMakePair(key, value) ); i++; } } KQOAuthParameters KQOAuthRequest::additionalParameters() const { Q_D(const KQOAuthRequest); QMultiMap additionalParams; for(int i=0; iadditionalParameters.size(); i++) { additionalParams.insert(d->additionalParameters.at(i).first, d->additionalParameters.at(i).second); } if(d->requestOAuthMethod == KQOAuthRequest::OAUTH2) { additionalParams.insert(OAUTH_KEY_TOKEN, d->oauthToken); } return additionalParams; } KQOAuthRequest::RequestType KQOAuthRequest::requestType() const { Q_D(const KQOAuthRequest); return d->requestType; } QUrl KQOAuthRequest::requestEndpoint() const { Q_D(const KQOAuthRequest); return d->oauthRequestEndpoint; } QList KQOAuthRequest::requestParameters() { Q_D(KQOAuthRequest); QList requestParamList; d->prepareRequest(); if (!isValid() ) { qWarning() << "Request is not valid! I will still sign it, but it will probably not work."; } if(d->requestOAuthMethod != KQOAuthRequest::OAUTH2) { d->signRequest(); } QPair requestParam; QString param; QString value; foreach (requestParam, d->requestParameters) { param = requestParam.first; value = requestParam.second; requestParamList.append(QString(param + "=\"" + value +"\"").toUtf8()); } return requestParamList; } QString KQOAuthRequest::contentType() { Q_D(const KQOAuthRequest); return d->contentType; } void KQOAuthRequest::setContentType(const QString &contentType) { Q_D(KQOAuthRequest); d->contentType = contentType; } QByteArray KQOAuthRequest::rawData() { Q_D(const KQOAuthRequest); return d->postRawData; } void KQOAuthRequest::setRawData(const QByteArray &rawData) { Q_D(KQOAuthRequest); d->postRawData = rawData; } QByteArray KQOAuthRequest::requestBody() const { Q_D(const KQOAuthRequest); QByteArray postBodyContent; bool first = true; for(int i=0; i < d->additionalParameters.size(); i++) { if(!first) { postBodyContent.append("&"); } else { first = false; } QString key = d->additionalParameters.at(i).first; QString value = d->additionalParameters.at(i).second; postBodyContent.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); } qDebug() << postBodyContent; return postBodyContent; } bool KQOAuthRequest::isValid() const { Q_D(const KQOAuthRequest); return d->validateRequest(); } void KQOAuthRequest::setTimeout(int timeoutMilliseconds) { Q_D(KQOAuthRequest); d->timeout = timeoutMilliseconds; } void KQOAuthRequest::clearRequest() { Q_D(KQOAuthRequest); d->oauthRequestEndpoint = ""; d->oauthHttpMethodString = ""; d->oauthConsumerKey = ""; d->oauthConsumerSecretKey = ""; d->oauthToken = ""; d->oauthTokenSecret = ""; d->oauthSignatureMethod = ""; d->oauthCallbackUrl = ""; d->oauthVerifier = ""; d->oauthTimestamp_ = ""; d->oauthNonce_ = ""; d->requestParameters.clear(); d->additionalParameters.clear(); d->timeout = 0; } void KQOAuthRequest::setEnableDebugOutput(bool enabled) { Q_D(KQOAuthRequest); d->debugOutput = enabled; } /** * Protected implementations for inherited classes */ bool KQOAuthRequest::validateXAuthRequest() const { Q_D(const KQOAuthRequest); if (d->oauthRequestEndpoint.isEmpty() || d->oauthConsumerKey.isEmpty() || d->oauthNonce_.isEmpty() || d->oauthSignatureMethod.isEmpty() || d->oauthTimestamp_.isEmpty() || d->oauthVersion.isEmpty()) { return false; } return true; } bool KQOAuthRequest::validateOauth2Request() const { Q_D(const KQOAuthRequest); if (d->oauthRequestEndpoint.isEmpty() || d->oauthToken.isEmpty()) { return false; } return true; } /** * Private implementations for friend classes */ QString KQOAuthRequest::consumerKeyForManager() const { Q_D(const KQOAuthRequest); return d->oauthConsumerKey; } QString KQOAuthRequest::consumerKeySecretForManager() const { Q_D(const KQOAuthRequest); return d->oauthConsumerSecretKey; } QUrl KQOAuthRequest::callbackUrlForManager() const { Q_D(const KQOAuthRequest); return d->oauthCallbackUrl; } void KQOAuthRequest::requestTimerStart() { Q_D(KQOAuthRequest); if (d->timeout > 0) { connect(&(d->timer), SIGNAL(timeout()), this, SIGNAL(requestTimedout())); d->timer.start(d->timeout); } } void KQOAuthRequest::requestTimerStop() { Q_D(KQOAuthRequest); if (d->timeout > 0) { disconnect(&(d->timer), SIGNAL(timeout()), this, SIGNAL(requestTimedout())); d->timer.stop(); } }