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 "kqoauthmanager.h"
23 #include "kqoauthmanager_p.h"
26 ////////////// Private d_ptr implementation ////////////////
28 KQOAuthManagerPrivate::KQOAuthManagerPrivate(KQOAuthManager *parent) :
29 error(KQOAuthManager::NoError) ,
31 opaqueRequest(new KQOAuthRequest) ,
33 callbackServer(new KQOAuthAuthReplyServer(parent)) ,
37 networkManager(new QNetworkAccessManager),
43 KQOAuthManagerPrivate::~KQOAuthManagerPrivate() {
47 if (!managerUserSet) {
48 delete networkManager;
53 QList< QPair<QString, QString> > KQOAuthManagerPrivate::createQueryParams(const KQOAuthParameters &requestParams) {
54 QList<QString> requestKeys = requestParams.keys();
55 QList<QString> requestValues = requestParams.values();
57 QList< QPair<QString, QString> > result;
58 for(int i=0; i<requestKeys.size(); i++) {
59 result.append( qMakePair(requestKeys.at(i),
67 QMultiMap<QString, QString> KQOAuthManagerPrivate::createTokensFromResponse(QByteArray reply) {
68 QMultiMap<QString, QString> result;
69 QString replyString(reply);
71 QStringList parameterPairs = replyString.split('&', QString::SkipEmptyParts);
72 foreach (const QString ¶meterPair, parameterPairs) {
73 QStringList parameter = parameterPair.split('=');
74 result.insert(parameter.value(0), parameter.value(1));
80 bool KQOAuthManagerPrivate::setSuccessfulRequestToken(const QMultiMap<QString, QString> &request) {
81 if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
82 hasTemporaryToken = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
87 if (hasTemporaryToken) {
88 requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
89 requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
92 return hasTemporaryToken;
95 bool KQOAuthManagerPrivate::setSuccessfulAuthorized(const QMultiMap<QString, QString> &request ) {
96 if (currentRequestType == KQOAuthRequest::AccessToken) {
97 isAuthorized = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
103 requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
104 requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
110 void KQOAuthManagerPrivate::emitTokens() {
113 if (this->requestToken.isEmpty() || this->requestTokenSecret.isEmpty()) {
114 error = KQOAuthManager::RequestUnauthorized;
117 if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
118 // Signal that we are ready to use the protected resources.
119 emit q->temporaryTokenReceived(this->requestToken, this->requestTokenSecret);
122 if (currentRequestType == KQOAuthRequest::AccessToken) {
123 // Signal that we are ready to use the protected resources.
124 emit q->accessTokenReceived(this->requestToken, this->requestTokenSecret);
127 emit q->receivedToken(this->requestToken, this->requestTokenSecret);
130 bool KQOAuthManagerPrivate::setupCallbackServer() {
131 return callbackServer->listen();
135 /////////////// Public implementation ////////////////
137 KQOAuthManager::KQOAuthManager(QObject *parent) :
139 d_ptr(new KQOAuthManagerPrivate(this))
144 KQOAuthManager::~KQOAuthManager()
149 void KQOAuthManager::executeRequest(KQOAuthRequest *request) {
155 qWarning() << "Request is NULL. Cannot proceed.";
156 d->error = KQOAuthManager::RequestError;
160 if (!request->requestEndpoint().isValid()) {
161 qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
162 d->error = KQOAuthManager::RequestEndpointError;
166 if (!request->isValid()) {
167 qWarning() << "Request is not valid. Cannot proceed.";
168 d->error = KQOAuthManager::RequestValidationError;
172 d->currentRequestType = request->requestType();
174 QNetworkRequest networkRequest;
175 networkRequest.setUrl( request->requestEndpoint() );
177 if (d->autoAuth && d->currentRequestType == KQOAuthRequest::TemporaryCredentials) {
178 d->setupCallbackServer();
179 connect(d->callbackServer, SIGNAL(verificationReceived(QMultiMap<QString, QString>)),
180 this, SLOT( onVerificationReceived(QMultiMap<QString, QString>)));
182 QString serverString = "http://localhost:";
183 serverString.append(QString::number(d->callbackServer->serverPort()));
184 request->setCallbackUrl(QUrl(serverString));
187 // And now fill the request with "Authorization" header data.
188 QList<QByteArray> requestHeaders = request->requestParameters();
189 QByteArray authHeader;
192 foreach (const QByteArray header, requestHeaders) {
194 authHeader.append(", ");
196 authHeader.append("OAuth ");
200 authHeader.append(header);
202 networkRequest.setRawHeader("Authorization", authHeader);
204 connect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
205 this, SLOT(onRequestReplyReceived(QNetworkReply *)), Qt::UniqueConnection);
206 disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
207 this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *)));
209 if (request->httpMethod() == KQOAuthRequest::GET) {
210 // Get the requested additional params as a list of pairs we can give QUrl
211 QList< QPair<QString, QString> > urlParams = d->createQueryParams(request->additionalParameters());
213 // Take the original URL and append the query params to it.
214 QUrl urlWithParams = networkRequest.url();
215 urlWithParams.setQueryItems(urlParams);
216 networkRequest.setUrl(urlWithParams);
218 // Submit the request including the params.
219 QNetworkReply *reply = d->networkManager->get(networkRequest);
220 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
221 this, SLOT(slotError(QNetworkReply::NetworkError)));
223 } else if (request->httpMethod() == KQOAuthRequest::POST) {
225 networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
227 qDebug() << networkRequest.rawHeaderList();
228 qDebug() << networkRequest.rawHeader("Authorization");
229 qDebug() << networkRequest.rawHeader("Content-Type");
231 QNetworkReply *reply;
232 if (request->contentType() == "application/x-www-form-urlencoded") {
233 reply = d->networkManager->post(networkRequest, request->requestBody());
235 reply = d->networkManager->post(networkRequest, request->rawData());
238 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
239 this, SLOT(slotError(QNetworkReply::NetworkError)));
242 d->r->requestTimerStart();
245 void KQOAuthManager::executeAuthorizedRequest(KQOAuthRequest *request, int id) {
251 qWarning() << "Request is NULL. Cannot proceed.";
252 d->error = KQOAuthManager::RequestError;
256 if (!request->requestEndpoint().isValid()) {
257 qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
258 d->error = KQOAuthManager::RequestEndpointError;
262 if (!request->isValid()) {
263 qWarning() << "Request is not valid. Cannot proceed.";
264 d->error = KQOAuthManager::RequestValidationError;
268 d->currentRequestType = request->requestType();
270 QNetworkRequest networkRequest;
271 networkRequest.setUrl( request->requestEndpoint() );
273 if ( d->currentRequestType != KQOAuthRequest::AuthorizedRequest){
274 qWarning() << "Not Authorized Request. Cannot proceed";
275 d->error = KQOAuthManager::RequestError;
280 // And now fill the request with "Authorization" header data.
281 QList<QByteArray> requestHeaders = request->requestParameters();
282 QByteArray authHeader;
285 foreach (const QByteArray header, requestHeaders) {
287 authHeader.append(", ");
289 authHeader.append("OAuth ");
293 authHeader.append(header);
295 networkRequest.setRawHeader("Authorization", authHeader);
298 disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
299 this, SLOT(onRequestReplyReceived(QNetworkReply *)));
300 connect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
301 this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply*)), Qt::UniqueConnection);
303 if (request->httpMethod() == KQOAuthRequest::GET) {
304 // Get the requested additional params as a list of pairs we can give QUrl
305 QList< QPair<QString, QString> > urlParams = d->createQueryParams(request->additionalParameters());
307 // Take the original URL and append the query params to it.
308 QUrl urlWithParams = networkRequest.url();
309 urlWithParams.setQueryItems(urlParams);
310 networkRequest.setUrl(urlWithParams);
312 // Submit the request including the params.
313 QNetworkReply *reply = d->networkManager->get(networkRequest);
314 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
315 this, SLOT(slotError(QNetworkReply::NetworkError)));
317 } else if (request->httpMethod() == KQOAuthRequest::POST) {
319 networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
322 qDebug() << networkRequest.rawHeaderList();
323 qDebug() << networkRequest.rawHeader("Authorization");
324 qDebug() << networkRequest.rawHeader("Content-Type");
326 QNetworkReply *reply;
327 if (request->contentType() == "application/x-www-form-urlencoded") {
328 reply = d->networkManager->post(networkRequest, request->requestBody());
330 reply = d->networkManager->post(networkRequest, request->rawData());
333 d->requestIds.insert(reply, id);
335 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
336 this, SLOT(slotError(QNetworkReply::NetworkError)));
339 d->r->requestTimerStart();
343 void KQOAuthManager::setHandleUserAuthorization(bool set) {
349 bool KQOAuthManager::hasTemporaryToken() {
352 return d->hasTemporaryToken;
355 bool KQOAuthManager::isVerified() {
358 return d->isVerified;
361 bool KQOAuthManager::isAuthorized() {
364 return d->isAuthorized;
367 KQOAuthManager::KQOAuthError KQOAuthManager::lastError() {
373 void KQOAuthManager::setNetworkManager(QNetworkAccessManager *manager) {
377 d->error = KQOAuthManager::ManagerError;
381 if (!d->managerUserSet) {
382 delete d->networkManager;
385 d->managerUserSet = true;
386 d->networkManager = manager;
389 QNetworkAccessManager * KQOAuthManager::networkManager() const {
390 Q_D(const KQOAuthManager);
392 if (d->managerUserSet) {
393 return d->networkManager;
401 //////////// Public convenience API /////////////
403 QUrl KQOAuthManager::getUserAuthorization(QUrl authorizationEndpoint) {
406 if (!d->hasTemporaryToken) {
407 qWarning() << "No temporary tokens retreieved. Cannot get user authorization.";
408 d->error = KQOAuthManager::RequestUnauthorized;
412 if (!authorizationEndpoint.isValid()) {
413 qWarning() << "Authorization endpoint not valid. Cannot proceed.";
414 d->error = KQOAuthManager::RequestEndpointError;
418 d->error = KQOAuthManager::NoError;
420 QPair<QString, QString> tokenParam = qMakePair(QString("oauth_token"), QString(d->requestToken));
421 QUrl openWebPageUrl(authorizationEndpoint.toString(), QUrl::StrictMode);
422 openWebPageUrl.addQueryItem(tokenParam.first, tokenParam.second);
424 // Return the resource authorization page provided by the service.
425 qDebug() << "KQOAuthManager::getUserAuthorization " << openWebPageUrl;
426 return openWebPageUrl;
429 void KQOAuthManager::getUserAccessTokens(QUrl accessTokenEndpoint) {
432 if (!d->isVerified) {
433 qWarning() << "Not verified. Cannot get access tokens.";
434 d->error = KQOAuthManager::RequestUnauthorized;
438 if (!accessTokenEndpoint.isValid()) {
439 qWarning() << "Endpoint for access token exchange is not valid. Cannot proceed.";
440 d->error = KQOAuthManager::RequestEndpointError;
444 d->error = KQOAuthManager::NoError;
446 d->opaqueRequest->clearRequest();
447 d->opaqueRequest->initRequest(KQOAuthRequest::AccessToken, accessTokenEndpoint);
448 d->opaqueRequest->setToken(d->requestToken);
449 d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
450 d->opaqueRequest->setVerifier(d->requestVerifier);
451 d->opaqueRequest->setConsumerKey(d->consumerKey);
452 d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
454 executeRequest(d->opaqueRequest);
457 void KQOAuthManager::sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters) {
460 if (!d->isAuthorized) {
461 qWarning() << "No access tokens retrieved. Cannot send authorized requests.";
462 d->error = KQOAuthManager::RequestUnauthorized;
466 if (!requestEndpoint.isValid()) {
467 qWarning() << "Endpoint for authorized request is not valid. Cannot proceed.";
468 d->error = KQOAuthManager::RequestEndpointError;
472 d->error = KQOAuthManager::NoError;
474 d->opaqueRequest->clearRequest();
475 d->opaqueRequest->initRequest(KQOAuthRequest::AuthorizedRequest, requestEndpoint);
476 d->opaqueRequest->setAdditionalParameters(requestParameters);
477 d->opaqueRequest->setToken(d->requestToken);
478 d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
479 d->opaqueRequest->setConsumerKey(d->consumerKey);
480 d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
482 executeRequest(d->opaqueRequest);
486 /////////////// Private slots //////////////////
488 void KQOAuthManager::onRequestReplyReceived( QNetworkReply *reply ) {
491 QNetworkReply::NetworkError networkError = reply->error();
492 switch (networkError) {
493 case QNetworkReply::NoError:
494 d->error = KQOAuthManager::NoError;
497 case QNetworkReply::ContentAccessDenied:
498 case QNetworkReply::AuthenticationRequiredError:
499 d->error = KQOAuthManager::RequestUnauthorized;
503 d->error = KQOAuthManager::NetworkError;
507 // Let's disconnect this slot first
509 disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
510 this, SLOT(onRequestReplyReceived(QNetworkReply *)));
513 // Read the content of the reply from the network.
514 QByteArray networkReply = reply->readAll();
516 // Stop any timer we have set on the request.
517 d->r->requestTimerStop();
519 // Just don't do anything if we didn't get anything useful.
520 if(networkReply.isEmpty()) {
521 reply->deleteLater();
524 QMultiMap<QString, QString> responseTokens;
526 // We need to emit the signal even if we got an error.
527 if (d->error != KQOAuthManager::NoError) {
528 reply->deleteLater();
529 emit requestReady(networkReply);
534 responseTokens = d->createTokensFromResponse(networkReply);
535 d->opaqueRequest->clearRequest();
536 d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET
537 if (!d->isAuthorized || !d->isVerified) {
538 if (d->setSuccessfulRequestToken(responseTokens)) {
539 qDebug() << "Successfully got request tokens.";
540 d->consumerKey = d->r->consumerKeyForManager();
541 d->consumerKeySecret = d->r->consumerKeySecretForManager();
542 d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
543 d->opaqueRequest->setCallbackUrl(d->r->callbackUrlForManager());
547 } else if (d->setSuccessfulAuthorized(responseTokens)) {
548 qDebug() << "Successfully got access tokens.";
549 d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
552 } else if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
553 emit authorizedRequestDone();
557 emit requestReady(networkReply);
559 reply->deleteLater(); // We need to clean this up, after the event processing is done.
562 void KQOAuthManager::onAuthorizedRequestReplyReceived( QNetworkReply *reply ) {
565 QNetworkReply::NetworkError networkError = reply->error();
566 switch (networkError) {
567 case QNetworkReply::NoError:
568 d->error = KQOAuthManager::NoError;
571 case QNetworkReply::ContentAccessDenied:
572 case QNetworkReply::AuthenticationRequiredError:
573 d->error = KQOAuthManager::RequestUnauthorized;
577 d->error = KQOAuthManager::NetworkError;
582 disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
583 this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *)));
586 // Read the content of the reply from the network.
587 QByteArray networkReply = reply->readAll();
589 // Stop any timer we have set on the request.
590 d->r->requestTimerStop();
592 // Just don't do anything if we didn't get anything useful.
593 if(networkReply.isEmpty()) {
594 reply->deleteLater();
598 // We need to emit the signal even if we got an error.
599 if (d->error != KQOAuthManager::NoError) {
600 qWarning() << "Network reply error";
605 d->opaqueRequest->clearRequest();
606 d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET
607 if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
608 emit authorizedRequestDone();
611 int id = d->requestIds.take(reply);
612 emit authorizedRequestReady(networkReply, id);
613 reply->deleteLater();
617 void KQOAuthManager::onVerificationReceived(QMultiMap<QString, QString> response) {
620 QString token = response.value("oauth_token");
621 QString verifier = response.value("oauth_verifier");
622 if (verifier.isEmpty()) {
623 d->error = KQOAuthManager::RequestUnauthorized;
626 verifier = QUrl::fromPercentEncoding(verifier.toUtf8()); // We get the raw URL response here so we need to convert it back
627 // to plain string so we can percent encode it again later in requests.
629 if (d->error == KQOAuthManager::NoError) {
630 d->requestVerifier = verifier;
631 d->isVerified = true;
634 emit authorizationReceived(token, verifier);
637 void KQOAuthManager::slotError(QNetworkReply::NetworkError error) {
641 d->error = KQOAuthManager::NetworkError;
642 QByteArray emptyResponse;
643 emit requestReady(emptyResponse);
644 emit authorizedRequestDone();
646 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
647 d->requestIds.remove(reply);
648 reply->deleteLater();