bb-cascades-oauth/oauth/kqoauthrequest.cpp
2012-04-18 23:48:41 -07:00

621 lines
19 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 <http://www.gnu.org/licenses/>.
*/
#include <QByteArray>
#include <QDateTime>
#include <QCryptographicHash>
#include <QPair>
#include <QStringList>
#include <QtDebug>
#include <QtAlgorithms>
#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<QString, QString> &left, const QPair<QString, QString> &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<QString, QString> > 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<QString, QString> > &parameters) {
QByteArray resultList;
bool first = true;
QPair<QString, QString> 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<QString> additionalKeys = additionalParams.keys();
QList<QString> 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<QString, QString> additionalParams;
for(int i=0; i<d->additionalParameters.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<QByteArray> KQOAuthRequest::requestParameters() {
Q_D(KQOAuthRequest);
QList<QByteArray> 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<QString, QString> 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();
}
}