/**
* KQOAuth - An OAuth authentication library for Qt.
*
* Author: Johan Paul (johan.paul@d-pointer.com)
* http://www.d-pointer.com
*
* This library 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 2.1 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.
*
* In addition, as a special exception, KQOauth provides you certain additional
* rights. These rights are described in the Nokia Qt LGPL Exception
* version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
*
* You should have received a copy of the GNU Lesser General Public License
* along with KQOAuth. If not, see
*/
#include
#include "kqoauthmanager.h"
#include "kqoauthmanager_p.h"
////////////// Private d_ptr implementation ////////////////
KQOAuthManagerPrivate::KQOAuthManagerPrivate(KQOAuthManager *parent) :
error(KQOAuthManager::NoError) ,
r(0) ,
opaqueRequest(new KQOAuthRequest) ,
q_ptr(parent) ,
callbackServer(new KQOAuthAuthReplyServer(parent)) ,
isVerified(false) ,
isAuthorized(false) ,
autoAuth(false),
networkManager(new QNetworkAccessManager),
managerUserSet(false)
{
}
KQOAuthManagerPrivate::~KQOAuthManagerPrivate() {
delete opaqueRequest;
opaqueRequest = 0;
if (!managerUserSet) {
delete networkManager;
networkManager = 0;
}
}
QList< QPair > KQOAuthManagerPrivate::createQueryParams(const KQOAuthParameters &requestParams) {
QList requestKeys = requestParams.keys();
QList requestValues = requestParams.values();
QList< QPair > result;
for(int i=0; i KQOAuthManagerPrivate::createTokensFromResponse(QByteArray reply) {
QMultiMap result;
QString replyString(reply);
QStringList parameterPairs = replyString.split('&', QString::SkipEmptyParts);
foreach (const QString ¶meterPair, parameterPairs) {
QStringList parameter = parameterPair.split('=');
result.insert(parameter.value(0), parameter.value(1));
}
return result;
}
bool KQOAuthManagerPrivate::setSuccessfulRequestToken(const QMultiMap &request) {
if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
hasTemporaryToken = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
} else {
return false;
}
if (hasTemporaryToken) {
requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
}
return hasTemporaryToken;
}
bool KQOAuthManagerPrivate::setSuccessfulAuthorized(const QMultiMap &request ) {
if (currentRequestType == KQOAuthRequest::AccessToken) {
isAuthorized = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
} else {
return false;
}
if (isAuthorized) {
requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
}
return isAuthorized;
}
void KQOAuthManagerPrivate::emitTokens() {
Q_Q(KQOAuthManager);
if (this->requestToken.isEmpty() || this->requestTokenSecret.isEmpty()) {
error = KQOAuthManager::RequestUnauthorized;
}
if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
// Signal that we are ready to use the protected resources.
emit q->temporaryTokenReceived(this->requestToken, this->requestTokenSecret);
}
if (currentRequestType == KQOAuthRequest::AccessToken) {
// Signal that we are ready to use the protected resources.
emit q->accessTokenReceived(this->requestToken, this->requestTokenSecret);
}
emit q->receivedToken(this->requestToken, this->requestTokenSecret);
}
bool KQOAuthManagerPrivate::setupCallbackServer() {
return callbackServer->listen(QHostAddress::LocalHost, CALLBACK_PORT);
}
/////////////// Public implementation ////////////////
KQOAuthManager::KQOAuthManager(QObject *parent) :
QObject(parent) ,
d_ptr(new KQOAuthManagerPrivate(this))
{
}
KQOAuthManager::~KQOAuthManager()
{
delete d_ptr;
}
void KQOAuthManager::executeRequest(KQOAuthRequest *request) {
Q_D(KQOAuthManager);
d->r = request;
if (request == 0) {
qWarning() << "Request is NULL. Cannot proceed.";
d->error = KQOAuthManager::RequestError;
return;
}
if (!request->requestEndpoint().isValid()) {
qDebug() << request->requestEndpoint();
qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestEndpointError;
return;
}
if (!request->isValid()) {
qWarning() << "Request is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestValidationError;
return;
}
d->currentRequestType = request->requestType();
QNetworkRequest networkRequest;
networkRequest.setUrl( request->requestEndpoint() );
if (d->autoAuth && d->currentRequestType == KQOAuthRequest::TemporaryCredentials) {
d->setupCallbackServer();
connect(d->callbackServer, SIGNAL(verificationReceived(QMultiMap)),
this, SLOT( onVerificationReceived(QMultiMap)));
QString serverString = "http://localhost:";
serverString.append(QString::number(d->callbackServer->serverPort()));
request->setCallbackUrl(QUrl(serverString));
qDebug() << serverString;
}
// And now fill the request with "Authorization" header data.
QList requestHeaders = request->requestParameters();
QByteArray authHeader;
bool first = true;
foreach (const QByteArray header, requestHeaders) {
if (!first) {
authHeader.append(", ");
} else {
authHeader.append("OAuth ");
first = false;
}
authHeader.append(header);
}
networkRequest.setRawHeader("Authorization", authHeader);
connect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onRequestReplyReceived(QNetworkReply *)), Qt::UniqueConnection);
disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *)));
if (request->httpMethod() == KQOAuthRequest::GET) {
// Get the requested additional params as a list of pairs we can give QUrl
QList< QPair > urlParams = d->createQueryParams(request->additionalParameters());
// Take the original URL and append the query params to it.
QUrl urlWithParams = networkRequest.url();
urlWithParams.setQueryItems(urlParams);
networkRequest.setUrl(urlWithParams);
// Submit the request including the params.
QNetworkReply *reply = d->networkManager->get(networkRequest);
reply->ignoreSslErrors();
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
} else if (request->httpMethod() == KQOAuthRequest::POST || request->httpMethod() == KQOAuthRequest::DELETE || request->httpMethod() == KQOAuthRequest::PUT) {
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
qDebug() << networkRequest.rawHeaderList();
qDebug() << networkRequest.rawHeader("Authorization");
qDebug() << networkRequest.rawHeader("Content-Type");
QNetworkReply *reply;
if (request->httpMethod() == KQOAuthRequest::PUT) {
reply = d->networkManager->put(networkRequest, request->requestBody());
} else if (request->httpMethod() == KQOAuthRequest::DELETE) {
reply = d->networkManager->deleteResource(networkRequest);
} else if (request->contentType() == "application/x-www-form-urlencoded") {
reply = d->networkManager->post(networkRequest, request->requestBody());
} else {
reply = d->networkManager->post(networkRequest, request->rawData());
}
reply->ignoreSslErrors();
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
}
d->r->requestTimerStart();
}
void KQOAuthManager::executeAuthorizedRequest(KQOAuthRequest *request, int id) {
Q_D(KQOAuthManager);
d->r = request;
if (request == 0) {
qWarning() << "Request is NULL. Cannot proceed.";
d->error = KQOAuthManager::RequestError;
return;
}
if (!request->requestEndpoint().isValid()) {
qDebug() << request->requestEndpoint();
qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestEndpointError;
return;
}
if (!request->isValid()) {
qWarning() << "Request is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestValidationError;
return;
}
d->currentRequestType = request->requestType();
QNetworkRequest networkRequest;
networkRequest.setUrl( request->requestEndpoint() );
if ( d->currentRequestType != KQOAuthRequest::AuthorizedRequest){
qWarning() << "Not Authorized Request. Cannot proceed";
d->error = KQOAuthManager::RequestError;
return;
}
if(request->oauthMethod() != KQOAuthRequest::OAUTH2) {
// And now fill the request with "Authorization" header data.
QList requestHeaders = request->requestParameters();
QByteArray authHeader;
bool first = true;
foreach (const QByteArray header, requestHeaders) {
if (!first) {
authHeader.append(", ");
} else {
authHeader.append("OAuth ");
first = false;
}
authHeader.append(header);
}
networkRequest.setRawHeader("Authorization", authHeader);
}
if(request->oauthMethod() == KQOAuthRequest::OAUTH2) {
QByteArray bearer;
bearer.append(request->additionalParameters().value("oauth_token"));
networkRequest.setRawHeader("Authorization", "Bearer " + bearer);
qDebug() << networkRequest.rawHeader("Authorization");
}
disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onRequestReplyReceived(QNetworkReply *)));
connect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply*)), Qt::UniqueConnection);
if (request->httpMethod() == KQOAuthRequest::GET) {
// Pull the oauth_token parameter because Dropbox
KQOAuthParameters adtlparams = request->additionalParameters();
adtlparams.remove("oauth_token");
qDebug() << adtlparams;
// Get the requested additional params as a list of pairs we can give QUrl
// QList< QPair > urlParams = d->createQueryParams(request->additionalParameters());
QList< QPair > urlParams = d->createQueryParams(adtlparams);
// Take the original URL and append the query params to it.
QUrl urlWithParams = networkRequest.url();
urlWithParams.setQueryItems(urlParams);
networkRequest.setUrl(urlWithParams);
qDebug() << networkRequest.url();
// Submit the request including the params.
QNetworkReply *reply = d->networkManager->get(networkRequest);
reply->ignoreSslErrors();
d->requestIds.insert(reply, id);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
} else if (request->httpMethod() == KQOAuthRequest::POST || request->httpMethod() == KQOAuthRequest::DELETE || request->httpMethod() == KQOAuthRequest::PUT) {
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
QByteArray argHeader;
argHeader.append(request->additionalParameters().value("arg"));
networkRequest.setRawHeader("Dropbox-API-Arg", argHeader);
qDebug() << networkRequest.rawHeaderList();
qDebug() << networkRequest.rawHeader("Authorization");
qDebug() << networkRequest.rawHeader("Content-Type");
QNetworkReply *reply;
if (request->httpMethod() == KQOAuthRequest::PUT) {
reply = d->networkManager->put(networkRequest, request->requestBody());
} else if (request->httpMethod() == KQOAuthRequest::DELETE) {
reply = d->networkManager->deleteResource(networkRequest);
} else if (request->contentType() == "application/x-www-form-urlencoded") {
reply = d->networkManager->post(networkRequest, request->requestBody());
} else {
reply = d->networkManager->post(networkRequest, request->rawData());
}
reply->ignoreSslErrors();
d->requestIds.insert(reply, id);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
}
d->r->requestTimerStart();
}
void KQOAuthManager::setHandleUserAuthorization(bool set) {
Q_D(KQOAuthManager);
d->autoAuth = set;
}
bool KQOAuthManager::hasTemporaryToken() {
Q_D(KQOAuthManager);
return d->hasTemporaryToken;
}
bool KQOAuthManager::isVerified() {
Q_D(KQOAuthManager);
return d->isVerified;
}
bool KQOAuthManager::isAuthorized() {
Q_D(KQOAuthManager);
return d->isAuthorized;
}
KQOAuthManager::KQOAuthError KQOAuthManager::lastError() {
Q_D(KQOAuthManager);
return d->error;
}
void KQOAuthManager::setNetworkManager(QNetworkAccessManager *manager) {
Q_D(KQOAuthManager);
if (manager == 0) {
d->error = KQOAuthManager::ManagerError;
return;
}
if (!d->managerUserSet) {
delete d->networkManager;
}
d->managerUserSet = true;
d->networkManager = manager;
}
QNetworkAccessManager * KQOAuthManager::networkManager() const {
Q_D(const KQOAuthManager);
if (d->managerUserSet) {
return d->networkManager;
} else {
return NULL;
}
}
void KQOAuthManager::setSuccessHtmlFile(QString file) {
Q_D(KQOAuthManager);
d->successHtmlFile = file;
d->callbackServer->setSuccessHtmlFile(file);
}
//////////// Public convenience API /////////////
void KQOAuthManager::getOauth2UserAuthorization(QUrl authorizationEndpoint, QString consumerKey, const KQOAuthParameters &additionalParams) {
Q_D(KQOAuthManager);
d->setupCallbackServer();
connect(d->callbackServer, SIGNAL(verificationReceived(QMultiMap)),
this, SLOT( onOauth2VerificationReceived(QMultiMap)));
QString serverString = "http://localhost:";
serverString.append(QString::number(d->callbackServer->serverPort()));
QUrl openWebPageUrl(authorizationEndpoint.toString(), QUrl::StrictMode);
openWebPageUrl.addQueryItem(OAUTH2_KEY_CLIENT_ID, consumerKey);
openWebPageUrl.addQueryItem(OAUTH2_KEY_RESPONSE_TYPE, "token");
openWebPageUrl.addQueryItem(OAUTH2_KEY_REDIRECT_URI, serverString);
if(additionalParams.size() > 0) {
QList< QPair > urlParams = d->createQueryParams(additionalParams);
for(int i=0; i < urlParams.length(); i++){
openWebPageUrl.addQueryItem(urlParams[i].first, urlParams[i].second);
}
}
qDebug() << openWebPageUrl.toString();
emit openBrowser(openWebPageUrl);
}
QUrl KQOAuthManager::getUserAuthorizationUrl(QUrl authorizationEndpoint) {
Q_D(KQOAuthManager);
if (!d->hasTemporaryToken) {
qWarning() << "No temporary tokens retreieved. Cannot get user authorization.";
d->error = KQOAuthManager::RequestUnauthorized;
return QUrl();
}
if (!authorizationEndpoint.isValid()) {
qWarning() << "Authorization endpoint not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestEndpointError;
return QUrl();
}
d->error = KQOAuthManager::NoError;
QPair tokenParam = qMakePair(QString("oauth_token"), QString(d->requestToken));
QUrl openWebPageUrl(authorizationEndpoint.toString(), QUrl::StrictMode);
openWebPageUrl.addQueryItem(tokenParam.first, tokenParam.second);
qDebug() << openWebPageUrl.toString();
return openWebPageUrl;
}
void KQOAuthManager::getUserAuthorization(QUrl authorizationEndpoint) {
QUrl openWebPageUrl = getUserAuthorizationUrl(authorizationEndpoint);
if(!openWebPageUrl.isEmpty()) {
// Open the user's default browser to the resource authorization page provided
// by the service.
emit openBrowser(openWebPageUrl);
}
}
void KQOAuthManager::getUserAccessTokens(QUrl accessTokenEndpoint) {
Q_D(KQOAuthManager);
if (!d->isVerified) {
qWarning() << "Not verified. Cannot get access tokens.";
d->error = KQOAuthManager::RequestUnauthorized;
return;
}
if (!accessTokenEndpoint.isValid()) {
qWarning() << "Endpoint for access token exchange is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestEndpointError;
return;
}
d->error = KQOAuthManager::NoError;
d->opaqueRequest->clearRequest();
d->opaqueRequest->initRequest(KQOAuthRequest::AccessToken, accessTokenEndpoint);
d->opaqueRequest->setToken(d->requestToken);
d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
d->opaqueRequest->setVerifier(d->requestVerifier);
d->opaqueRequest->setConsumerKey(d->consumerKey);
d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
executeRequest(d->opaqueRequest);
}
void KQOAuthManager::sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters) {
Q_D(KQOAuthManager);
if (!d->isAuthorized) {
qWarning() << "No access tokens retrieved. Cannot send authorized requests.";
d->error = KQOAuthManager::RequestUnauthorized;
return;
}
if (!requestEndpoint.isValid()) {
qWarning() << "Endpoint for authorized request is not valid. Cannot proceed.";
d->error = KQOAuthManager::RequestEndpointError;
return;
}
d->error = KQOAuthManager::NoError;
d->opaqueRequest->clearRequest();
d->opaqueRequest->initRequest(KQOAuthRequest::AuthorizedRequest, requestEndpoint);
d->opaqueRequest->setAdditionalParameters(requestParameters);
d->opaqueRequest->setToken(d->requestToken);
d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
d->opaqueRequest->setConsumerKey(d->consumerKey);
d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
executeRequest(d->opaqueRequest);
}
/////////////// Private slots //////////////////
void KQOAuthManager::onRequestReplyReceived( QNetworkReply *reply ) {
Q_D(KQOAuthManager);
QNetworkReply::NetworkError networkError = reply->error();
switch (networkError) {
case QNetworkReply::NoError:
d->error = KQOAuthManager::NoError;
break;
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::AuthenticationRequiredError:
d->error = KQOAuthManager::RequestUnauthorized;
break;
default:
d->error = KQOAuthManager::NetworkError;
break;
}
// Let's disconnect this slot first
/*
disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onRequestReplyReceived(QNetworkReply *)));
*/
// Read the content of the reply from the network.
QByteArray networkReply = reply->readAll();
// Stop any timer we have set on the request.
d->r->requestTimerStop();
// Just don't do anything if we didn't get anything useful.
if(networkReply.isEmpty()) {
reply->deleteLater();
return;
}
QMultiMap responseTokens;
// We need to emit the signal even if we got an error.
if (d->error != KQOAuthManager::NoError) {
reply->deleteLater();
emit requestReady(networkReply);
d->emitTokens();
return;
}
responseTokens = d->createTokensFromResponse(networkReply);
d->opaqueRequest->clearRequest();
d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET
if (!d->isAuthorized || !d->isVerified) {
if (d->setSuccessfulRequestToken(responseTokens)) {
qDebug() << "Successfully got request tokens.";
d->consumerKey = d->r->consumerKeyForManager();
d->consumerKeySecret = d->r->consumerKeySecretForManager();
d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
d->opaqueRequest->setCallbackUrl(d->r->callbackUrlForManager());
d->emitTokens();
} else if (d->setSuccessfulAuthorized(responseTokens)) {
qDebug() << "Successfully got access tokens.";
d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
d->emitTokens();
} else if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
emit authorizedRequestDone();
}
}
emit requestReady(networkReply);
reply->deleteLater(); // We need to clean this up, after the event processing is done.
}
void KQOAuthManager::onAuthorizedRequestReplyReceived( QNetworkReply *reply ) {
Q_D(KQOAuthManager);
QNetworkReply::NetworkError networkError = reply->error();
qDebug() << networkError;
switch (networkError) {
case QNetworkReply::NoError:
d->error = KQOAuthManager::NoError;
break;
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::AuthenticationRequiredError:
d->error = KQOAuthManager::RequestUnauthorized;
break;
default:
d->error = KQOAuthManager::NetworkError;
break;
}
/*
disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *)));
*/
// Read the content of the reply from the network.
QByteArray networkReply = reply->readAll();
qDebug() << networkReply;
// Stop any timer we have set on the request.
d->r->requestTimerStop();
// Just don't do anything if we didn't get anything useful.
if(networkReply.isEmpty()) {
qDebug() << "EMPTY REPLY";
reply->deleteLater();
return;
}
// We need to emit the signal even if we got an error.
if (d->error != KQOAuthManager::NoError) {
qWarning() << "Network reply error";
qDebug() << d->error;
emit requestReady(networkReply);
return;
}
d->opaqueRequest->clearRequest();
d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET
if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
emit authorizedRequestDone();
}
int id = d->requestIds.take(reply);
emit authorizedRequestReady(networkReply, id);
reply->deleteLater();
}
void KQOAuthManager::onVerificationReceived(QMultiMap response) {
Q_D(KQOAuthManager);
QString token = response.value("oauth_token");
QString verifier = response.value("oauth_verifier");
if (verifier.isEmpty()) {
d->error = KQOAuthManager::RequestUnauthorized;
}
verifier = QUrl::fromPercentEncoding(verifier.toUtf8()); // We get the raw URL response here so we need to convert it back
// to plain string so we can percent encode it again later in requests.
if (d->error == KQOAuthManager::NoError) {
d->requestVerifier = verifier;
d->isVerified = true;
}
emit authorizationReceived(token, verifier);
}
void KQOAuthManager::onOauth2VerificationReceived(QMultiMap response) {
Q_D(KQOAuthManager);
foreach(QString key, response.keys()){
qDebug() << key;
}
QString token = response.value("access_token");
if (token.isEmpty()) {
d->error = KQOAuthManager::RequestUnauthorized;
}
if (d->error == KQOAuthManager::NoError) {
d->isVerified = true;
}
emit authorizationReceived(token, NULL);
}
void KQOAuthManager::slotError(QNetworkReply::NetworkError error) {
Q_UNUSED(error)
Q_D(KQOAuthManager);
d->error = KQOAuthManager::NetworkError;
QByteArray emptyResponse;
emit requestReady(emptyResponse);
emit authorizedRequestDone();
QNetworkReply *reply = qobject_cast(sender());
d->requestIds.remove(reply);
reply->deleteLater();
}
void KQOAuthManager::onSslError(QNetworkReply *reply, const QList &errors) {
Q_UNUSED(errors);
reply->ignoreSslErrors();
}