b133ff701d71fcb379717427dbd35ddfadeecb8f
[googlelatitude] / libkqoauth / kqoauthmanager.cpp
1 /**
2  * KQOAuth - An OAuth authentication library for Qt.
3  *
4  * Author: Johan Paul (johan.paul@d-pointer.com)
5  *         http://www.d-pointer.com
6  *
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.
11  *
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.
16  *
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/>.
19  */
20 #include <QtCore>
21
22 #include "kqoauthmanager.h"
23 #include "kqoauthmanager_p.h"
24
25
26 ////////////// Private d_ptr implementation ////////////////
27
28 KQOAuthManagerPrivate::KQOAuthManagerPrivate(KQOAuthManager *parent) :
29     error(KQOAuthManager::NoError) ,
30     r(0) ,
31     opaqueRequest(new KQOAuthRequest) ,
32     q_ptr(parent) ,
33     callbackServer(new KQOAuthAuthReplyServer(parent)) ,
34     isVerified(false) ,
35     isAuthorized(false) ,
36     autoAuth(false),
37     networkManager(new QNetworkAccessManager),
38     managerUserSet(false)
39 {
40
41 }
42
43 KQOAuthManagerPrivate::~KQOAuthManagerPrivate() {
44     delete opaqueRequest;
45     opaqueRequest = 0;
46
47     if (!managerUserSet) {
48         delete networkManager;
49         networkManager = 0;
50     }
51 }
52
53 QList< QPair<QString, QString> > KQOAuthManagerPrivate::createQueryParams(const KQOAuthParameters &requestParams) {
54     QList<QString> requestKeys = requestParams.keys();
55     QList<QString> requestValues = requestParams.values();
56
57     QList< QPair<QString, QString> > result;
58     for(int i=0; i<requestKeys.size(); i++) {
59         result.append( qMakePair(requestKeys.at(i),
60                                  requestValues.at(i))
61                       );
62     }
63
64     return result;
65 }
66
67 QMultiMap<QString, QString> KQOAuthManagerPrivate::createTokensFromResponse(QByteArray reply) {
68     QMultiMap<QString, QString> result;
69     QString replyString(reply);
70
71     QStringList parameterPairs = replyString.split('&', QString::SkipEmptyParts);
72     foreach (const QString &parameterPair, parameterPairs) {
73         QStringList parameter = parameterPair.split('=');
74         result.insert(parameter.value(0), parameter.value(1));
75     }
76
77     return result;
78 }
79
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());
83     } else {
84         return false;
85     }
86
87     if (hasTemporaryToken) {
88         requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
89         requestTokenSecret =  QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
90     }
91
92     return hasTemporaryToken;
93 }
94
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());
98     } else {
99         return false;
100     }
101
102     if (isAuthorized) {
103         requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
104         requestTokenSecret =  QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
105     }
106
107     return isAuthorized;
108 }
109
110 void KQOAuthManagerPrivate::emitTokens() {
111     Q_Q(KQOAuthManager);
112
113     if (this->requestToken.isEmpty() || this->requestTokenSecret.isEmpty()) {
114         error = KQOAuthManager::RequestUnauthorized;
115     }
116
117     if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
118         // Signal that we are ready to use the protected resources.
119         emit q->temporaryTokenReceived(this->requestToken, this->requestTokenSecret);
120     }
121
122     if (currentRequestType == KQOAuthRequest::AccessToken) {
123         // Signal that we are ready to use the protected resources.
124         emit q->accessTokenReceived(this->requestToken, this->requestTokenSecret);
125     }
126
127     emit q->receivedToken(this->requestToken, this->requestTokenSecret);
128 }
129
130 bool KQOAuthManagerPrivate::setupCallbackServer() {
131     return callbackServer->listen();
132 }
133
134
135 /////////////// Public implementation ////////////////
136
137 KQOAuthManager::KQOAuthManager(QObject *parent) :
138     QObject(parent) ,
139     d_ptr(new KQOAuthManagerPrivate(this))
140 {
141
142 }
143
144 KQOAuthManager::~KQOAuthManager()
145 {
146     delete d_ptr;
147 }
148
149 void KQOAuthManager::executeRequest(KQOAuthRequest *request) {
150     Q_D(KQOAuthManager);
151
152     d->r = request;
153
154     if (request == 0) {
155         qWarning() << "Request is NULL. Cannot proceed.";
156         d->error = KQOAuthManager::RequestError;
157         return;
158     }
159
160     if (!request->requestEndpoint().isValid()) {
161         qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
162         d->error = KQOAuthManager::RequestEndpointError;
163         return;
164     }
165
166     if (!request->isValid()) {
167         qWarning() << "Request is not valid. Cannot proceed.";
168         d->error = KQOAuthManager::RequestValidationError;
169         return;
170     }
171
172     d->currentRequestType = request->requestType();
173
174     QNetworkRequest networkRequest;
175     networkRequest.setUrl( request->requestEndpoint() );
176
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>)));
181
182         QString serverString = "http://localhost:";
183         serverString.append(QString::number(d->callbackServer->serverPort()));
184         request->setCallbackUrl(QUrl(serverString));
185     }
186
187     // And now fill the request with "Authorization" header data.
188     QList<QByteArray> requestHeaders = request->requestParameters();
189     QByteArray authHeader;
190
191     bool first = true;
192     foreach (const QByteArray header, requestHeaders) {
193         if (!first) {
194             authHeader.append(", ");
195         } else {
196             authHeader.append("OAuth ");
197             first = false;
198         }
199
200         authHeader.append(header);
201     }
202     networkRequest.setRawHeader("Authorization", authHeader);
203
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 *)));
208
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());
212
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);
217
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)));
222
223     } else if (request->httpMethod() == KQOAuthRequest::POST) {
224
225         networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
226
227         qDebug() << networkRequest.rawHeaderList();
228         qDebug() << networkRequest.rawHeader("Authorization");
229         qDebug() << networkRequest.rawHeader("Content-Type");
230
231         QNetworkReply *reply;
232         if (request->contentType() == "application/x-www-form-urlencoded") {
233           reply = d->networkManager->post(networkRequest, request->requestBody());
234         } else {
235           reply = d->networkManager->post(networkRequest, request->rawData());
236         }
237
238         connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
239                  this, SLOT(slotError(QNetworkReply::NetworkError)));
240     }
241
242     d->r->requestTimerStart();
243 }
244
245 void KQOAuthManager::executeAuthorizedRequest(KQOAuthRequest *request, int id) {
246     Q_D(KQOAuthManager);
247
248     d->r = request;
249
250     if (request == 0) {
251         qWarning() << "Request is NULL. Cannot proceed.";
252         d->error = KQOAuthManager::RequestError;
253         return;
254     }
255
256     if (!request->requestEndpoint().isValid()) {
257         qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
258         d->error = KQOAuthManager::RequestEndpointError;
259         return;
260     }
261
262     if (!request->isValid()) {
263         qWarning() << "Request is not valid. Cannot proceed.";
264         d->error = KQOAuthManager::RequestValidationError;
265         return;
266     }
267
268     d->currentRequestType = request->requestType();
269
270     QNetworkRequest networkRequest;
271     networkRequest.setUrl( request->requestEndpoint() );
272
273     if ( d->currentRequestType != KQOAuthRequest::AuthorizedRequest){
274         qWarning() << "Not Authorized Request. Cannot proceed";
275         d->error = KQOAuthManager::RequestError;
276         return;
277     }
278
279
280     // And now fill the request with "Authorization" header data.
281     QList<QByteArray> requestHeaders = request->requestParameters();
282     QByteArray authHeader;
283
284     bool first = true;
285     foreach (const QByteArray header, requestHeaders) {
286         if (!first) {
287             authHeader.append(", ");
288         } else {
289             authHeader.append("OAuth ");
290             first = false;
291         }
292
293         authHeader.append(header);
294     }
295     networkRequest.setRawHeader("Authorization", authHeader);
296
297
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);
302
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());
306
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);
311
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)));
316
317     } else if (request->httpMethod() == KQOAuthRequest::POST) {
318
319         networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
320
321         /*
322         qDebug() << networkRequest.rawHeaderList();
323         qDebug() << networkRequest.rawHeader("Authorization");
324         qDebug() << networkRequest.rawHeader("Content-Type");
325         */
326         QNetworkReply *reply;
327         if (request->contentType() == "application/x-www-form-urlencoded") {
328           reply = d->networkManager->post(networkRequest, request->requestBody());
329         } else {
330           reply = d->networkManager->post(networkRequest, request->rawData());
331         }
332
333         d->requestIds.insert(reply, id);
334
335         connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
336                  this, SLOT(slotError(QNetworkReply::NetworkError)));
337     }
338
339     d->r->requestTimerStart();
340 }
341
342
343 void KQOAuthManager::setHandleUserAuthorization(bool set) {
344     Q_D(KQOAuthManager);
345
346     d->autoAuth = set;
347 }
348
349 bool KQOAuthManager::hasTemporaryToken() {
350     Q_D(KQOAuthManager);
351
352     return d->hasTemporaryToken;
353 }
354
355 bool KQOAuthManager::isVerified() {
356     Q_D(KQOAuthManager);
357
358     return d->isVerified;
359 }
360
361 bool KQOAuthManager::isAuthorized() {
362     Q_D(KQOAuthManager);
363
364     return d->isAuthorized;
365 }
366
367 KQOAuthManager::KQOAuthError KQOAuthManager::lastError() {
368     Q_D(KQOAuthManager);
369
370     return d->error;
371 }
372
373 void KQOAuthManager::setNetworkManager(QNetworkAccessManager *manager) {
374     Q_D(KQOAuthManager);
375
376     if (manager == 0) {
377         d->error = KQOAuthManager::ManagerError;
378         return;
379     }
380
381     if (!d->managerUserSet) {
382         delete d->networkManager;
383     }
384
385     d->managerUserSet = true;
386     d->networkManager = manager;
387 }
388
389 QNetworkAccessManager * KQOAuthManager::networkManager() const {
390     Q_D(const KQOAuthManager);
391
392     if (d->managerUserSet) {
393         return d->networkManager;
394     } else {
395         return NULL;
396     }
397
398 }
399
400
401 //////////// Public convenience API /////////////
402
403 QUrl KQOAuthManager::getUserAuthorization(QUrl authorizationEndpoint) {
404     Q_D(KQOAuthManager);
405
406     if (!d->hasTemporaryToken) {
407         qWarning() << "No temporary tokens retreieved. Cannot get user authorization.";
408         d->error = KQOAuthManager::RequestUnauthorized;
409         return QString();
410     }
411
412     if (!authorizationEndpoint.isValid()) {
413         qWarning() << "Authorization endpoint not valid. Cannot proceed.";
414         d->error = KQOAuthManager::RequestEndpointError;
415         return QString();
416     }
417
418     d->error = KQOAuthManager::NoError;
419
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);
423
424     // Return the resource authorization page provided by the service.
425     qDebug() << "KQOAuthManager::getUserAuthorization " << openWebPageUrl;
426     return openWebPageUrl;
427 }
428
429 void KQOAuthManager::getUserAccessTokens(QUrl accessTokenEndpoint) {
430     Q_D(KQOAuthManager);
431
432     if (!d->isVerified) {
433         qWarning() << "Not verified. Cannot get access tokens.";
434         d->error = KQOAuthManager::RequestUnauthorized;
435         return;
436     }
437
438     if (!accessTokenEndpoint.isValid()) {
439         qWarning() << "Endpoint for access token exchange is not valid. Cannot proceed.";
440         d->error = KQOAuthManager::RequestEndpointError;
441         return;
442     }
443
444     d->error = KQOAuthManager::NoError;
445
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);
453
454     executeRequest(d->opaqueRequest);
455 }
456
457 void KQOAuthManager::sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters) {
458     Q_D(KQOAuthManager);
459
460     if (!d->isAuthorized) {
461         qWarning() << "No access tokens retrieved. Cannot send authorized requests.";
462         d->error = KQOAuthManager::RequestUnauthorized;
463         return;
464     }
465
466     if (!requestEndpoint.isValid()) {
467         qWarning() << "Endpoint for authorized request is not valid. Cannot proceed.";
468         d->error = KQOAuthManager::RequestEndpointError;
469         return;
470     }
471
472     d->error = KQOAuthManager::NoError;
473
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);
481
482     executeRequest(d->opaqueRequest);
483 }
484
485
486 /////////////// Private slots //////////////////
487
488 void KQOAuthManager::onRequestReplyReceived( QNetworkReply *reply ) {
489     Q_D(KQOAuthManager);
490
491     QNetworkReply::NetworkError networkError = reply->error();
492     switch (networkError) {
493     case QNetworkReply::NoError:
494         d->error = KQOAuthManager::NoError;
495         break;
496
497     case QNetworkReply::ContentAccessDenied:
498     case QNetworkReply::AuthenticationRequiredError:
499         d->error = KQOAuthManager::RequestUnauthorized;
500         break;
501
502     default:
503         d->error = KQOAuthManager::NetworkError;
504         break;
505     }
506
507     // Let's disconnect this slot first
508     /*
509     disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
510             this, SLOT(onRequestReplyReceived(QNetworkReply *)));
511     */
512
513     // Read the content of the reply from the network.
514     QByteArray networkReply = reply->readAll();
515
516     // Stop any timer we have set on the request.
517     d->r->requestTimerStop();
518
519     // Just don't do anything if we didn't get anything useful.
520     if(networkReply.isEmpty()) {
521         reply->deleteLater();
522         return;
523     }
524     QMultiMap<QString, QString> responseTokens;
525
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);
530         d->emitTokens();
531         return;
532     }
533
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());
544
545             d->emitTokens();
546
547         } else if (d->setSuccessfulAuthorized(responseTokens)) {
548               qDebug() << "Successfully got access tokens.";
549               d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
550
551               d->emitTokens();
552           } else if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
553                 emit authorizedRequestDone();
554             }
555     }
556
557     emit requestReady(networkReply);
558
559     reply->deleteLater();           // We need to clean this up, after the event processing is done.
560 }
561
562 void KQOAuthManager::onAuthorizedRequestReplyReceived( QNetworkReply *reply ) {
563     Q_D(KQOAuthManager);
564
565     QNetworkReply::NetworkError networkError = reply->error();
566     switch (networkError) {
567     case QNetworkReply::NoError:
568         d->error = KQOAuthManager::NoError;
569         break;
570
571     case QNetworkReply::ContentAccessDenied:
572     case QNetworkReply::AuthenticationRequiredError:
573         d->error = KQOAuthManager::RequestUnauthorized;
574         break;
575
576     default:
577         d->error = KQOAuthManager::NetworkError;
578         break;
579     }
580
581     /*
582     disconnect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
583             this, SLOT(onAuthorizedRequestReplyReceived(QNetworkReply *)));
584     */
585
586     // Read the content of the reply from the network.
587     QByteArray networkReply = reply->readAll();
588
589     // Stop any timer we have set on the request.
590     d->r->requestTimerStop();
591
592     // Just don't do anything if we didn't get anything useful.
593     if(networkReply.isEmpty()) {
594         reply->deleteLater();
595         return;
596     }
597
598     // We need to emit the signal even if we got an error.
599     if (d->error != KQOAuthManager::NoError) {
600         qWarning() << "Network reply error";
601         return;
602     }
603
604
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();
609      }
610
611     int id = d->requestIds.take(reply);
612     emit authorizedRequestReady(networkReply, id);
613     reply->deleteLater();
614 }
615
616
617 void KQOAuthManager::onVerificationReceived(QMultiMap<QString, QString> response) {
618     Q_D(KQOAuthManager);
619
620     QString token = response.value("oauth_token");
621     QString verifier = response.value("oauth_verifier");
622     if (verifier.isEmpty()) {
623         d->error = KQOAuthManager::RequestUnauthorized;
624     }
625
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.
628
629     if (d->error == KQOAuthManager::NoError) {
630         d->requestVerifier = verifier;
631         d->isVerified = true;
632     }
633
634     emit authorizationReceived(token, verifier);
635 }
636
637 void KQOAuthManager::slotError(QNetworkReply::NetworkError error) {
638     Q_UNUSED(error)
639     Q_D(KQOAuthManager);
640
641     d->error = KQOAuthManager::NetworkError;
642     QByteArray emptyResponse;
643     emit requestReady(emptyResponse);
644     emit authorizedRequestDone();
645
646     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
647     d->requestIds.remove(reply);
648     reply->deleteLater();
649 }
650