/** * 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(); }