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/>.
21 // #include <QDesktopServices>
23 #include "kqoauthmanager.h"
24 #include "kqoauthmanager_p.h"
27 ////////////// Private d_ptr implementation ////////////////
29 KQOAuthManagerPrivate::KQOAuthManagerPrivate(KQOAuthManager *parent) :
30 error(KQOAuthManager::NoError) ,
32 opaqueRequest(new KQOAuthRequest) ,
34 callbackServer(new KQOAuthAuthReplyServer(parent)) ,
38 networkManager(new QNetworkAccessManager),
44 KQOAuthManagerPrivate::~KQOAuthManagerPrivate() {
48 if (!managerUserSet) {
49 delete networkManager;
54 QList< QPair<QString, QString> > KQOAuthManagerPrivate::createQueryParams(const KQOAuthParameters &requestParams) {
55 QList<QString> requestKeys = requestParams.keys();
56 QList<QString> requestValues = requestParams.values();
58 QList< QPair<QString, QString> > result;
59 for(int i=0; i<requestKeys.size(); i++) {
60 result.append( qMakePair(requestKeys.at(i),
68 QMultiMap<QString, QString> KQOAuthManagerPrivate::createTokensFromResponse(QByteArray reply) {
69 QMultiMap<QString, QString> result;
70 QString replyString(reply);
72 QStringList parameterPairs = replyString.split('&', QString::SkipEmptyParts);
73 foreach (const QString ¶meterPair, parameterPairs) {
74 QStringList parameter = parameterPair.split('=');
75 result.insert(parameter.value(0), parameter.value(1));
81 bool KQOAuthManagerPrivate::setSuccessfulRequestToken(const QMultiMap<QString, QString> &request) {
82 if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
83 hasTemporaryToken = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
88 if (hasTemporaryToken) {
89 requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
90 requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
93 return hasTemporaryToken;
96 bool KQOAuthManagerPrivate::setSuccessfulAuthorized(const QMultiMap<QString, QString> &request ) {
97 if (currentRequestType == KQOAuthRequest::AccessToken) {
98 isAuthorized = (!QString(request.value("oauth_token")).isEmpty() && !QString(request.value("oauth_token_secret")).isEmpty());
104 requestToken = QUrl::fromPercentEncoding( QString(request.value("oauth_token")).toLocal8Bit() );
105 requestTokenSecret = QUrl::fromPercentEncoding( QString(request.value("oauth_token_secret")).toLocal8Bit() );
111 void KQOAuthManagerPrivate::emitTokens() {
114 if (this->requestToken.isEmpty() || this->requestTokenSecret.isEmpty()) {
115 error = KQOAuthManager::RequestUnauthorized;
118 if (currentRequestType == KQOAuthRequest::TemporaryCredentials) {
119 // Signal that we are ready to use the protected resources.
120 emit q->temporaryTokenReceived(this->requestToken, this->requestTokenSecret);
123 if (currentRequestType == KQOAuthRequest::AccessToken) {
124 // Signal that we are ready to use the protected resources.
125 emit q->accessTokenReceived(this->requestToken, this->requestTokenSecret);
128 emit q->receivedToken(this->requestToken, this->requestTokenSecret);
131 bool KQOAuthManagerPrivate::setupCallbackServer() {
132 return callbackServer->listen();
136 /////////////// Public implementation ////////////////
138 KQOAuthManager::KQOAuthManager(QObject *parent) :
140 d_ptr(new KQOAuthManagerPrivate(this))
145 KQOAuthManager::~KQOAuthManager()
150 void KQOAuthManager::executeRequest(KQOAuthRequest *request) {
156 qWarning() << "Request is NULL. Cannot proceed.";
157 d->error = KQOAuthManager::RequestError;
161 if (!request->requestEndpoint().isValid()) {
162 qWarning() << "Request endpoint URL is not valid. Cannot proceed.";
163 d->error = KQOAuthManager::RequestEndpointError;
167 if (!request->isValid()) {
168 qWarning() << "Request is not valid. Cannot proceed.";
169 d->error = KQOAuthManager::RequestValidationError;
173 d->currentRequestType = request->requestType();
175 QNetworkRequest networkRequest;
176 networkRequest.setUrl( request->requestEndpoint() );
178 if (d->autoAuth && d->currentRequestType == KQOAuthRequest::TemporaryCredentials) {
179 d->setupCallbackServer();
180 connect(d->callbackServer, SIGNAL(verificationReceived(QMultiMap<QString, QString>)),
181 this, SLOT( onVerificationReceived(QMultiMap<QString, QString>)));
183 QString serverString = "http://localhost:";
184 serverString.append(QString::number(d->callbackServer->serverPort()));
185 request->setCallbackUrl(QUrl(serverString));
188 // And now fill the request with "Authorization" header data.
189 QList<QByteArray> requestHeaders = request->requestParameters();
190 QByteArray authHeader;
193 foreach (const QByteArray header, requestHeaders) {
195 authHeader.append(", ");
197 authHeader.append("OAuth ");
201 authHeader.append(header);
203 networkRequest.setRawHeader("Authorization", authHeader);
205 connect(d->networkManager, SIGNAL(finished(QNetworkReply *)),
206 this, SLOT(onRequestReplyReceived(QNetworkReply *)));
208 if (request->httpMethod() == KQOAuthRequest::GET) {
209 // Get the requested additional params as a list of pairs we can give QUrl
210 QList< QPair<QString, QString> > urlParams = d->createQueryParams(request->additionalParameters());
212 // Take the original URL and append the query params to it.
213 QUrl urlWithParams = networkRequest.url();
214 urlWithParams.setQueryItems(urlParams);
215 networkRequest.setUrl(urlWithParams);
217 // Submit the request including the params.
218 QNetworkReply *reply = d->networkManager->get(networkRequest);
219 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
220 this, SLOT(slotError(QNetworkReply::NetworkError)));
222 } else if (request->httpMethod() == KQOAuthRequest::POST) {
224 networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, request->contentType());
226 qDebug() << networkRequest.rawHeaderList();
227 qDebug() << networkRequest.rawHeader("Authorization");
228 qDebug() << networkRequest.rawHeader("Content-Type");
230 QNetworkReply *reply;
231 if (request->contentType() == "application/x-www-form-urlencoded") {
232 reply = d->networkManager->post(networkRequest, request->requestBody());
234 reply = d->networkManager->post(networkRequest, request->rawData());
237 connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
238 this, SLOT(slotError(QNetworkReply::NetworkError)));
241 d->r->requestTimerStart();
245 void KQOAuthManager::setHandleUserAuthorization(bool set) {
251 bool KQOAuthManager::hasTemporaryToken() {
254 return d->hasTemporaryToken;
257 bool KQOAuthManager::isVerified() {
260 return d->isVerified;
263 bool KQOAuthManager::isAuthorized() {
266 return d->isAuthorized;
269 KQOAuthManager::KQOAuthError KQOAuthManager::lastError() {
275 void KQOAuthManager::setNetworkManager(QNetworkAccessManager *manager) {
279 d->error = KQOAuthManager::ManagerError;
283 if (!d->managerUserSet) {
284 delete d->networkManager;
287 d->managerUserSet = true;
288 d->networkManager = manager;
291 QNetworkAccessManager * KQOAuthManager::networkManager() const {
292 Q_D(const KQOAuthManager);
294 if (d->managerUserSet) {
295 return d->networkManager;
303 //////////// Public convenience API /////////////
305 QUrl KQOAuthManager::getUserAuthorization(QUrl authorizationEndpoint) {
308 if (!d->hasTemporaryToken) {
309 qWarning() << "No temporary tokens retreieved. Cannot get user authorization.";
310 d->error = KQOAuthManager::RequestUnauthorized;
314 if (!authorizationEndpoint.isValid()) {
315 qWarning() << "Authorization endpoint not valid. Cannot proceed.";
316 d->error = KQOAuthManager::RequestEndpointError;
320 d->error = KQOAuthManager::NoError;
322 QPair<QString, QString> tokenParam = qMakePair(QString("oauth_token"), QString(d->requestToken));
323 QUrl openWebPageUrl(authorizationEndpoint.toString(), QUrl::StrictMode);
324 openWebPageUrl.addQueryItem(tokenParam.first, tokenParam.second);
326 // Open the user's default browser to the resource authorization page provided
328 // QDesktopServices::openUrl(openWebPageUrl);
329 qDebug() << "* KQOAuthManager::getUserAuthorization -> QDesktopServices::openUrl" << openWebPageUrl;
330 return openWebPageUrl;
333 void KQOAuthManager::getUserAccessTokens(QUrl accessTokenEndpoint) {
336 if (!d->isVerified) {
337 qWarning() << "Not verified. Cannot get access tokens.";
338 d->error = KQOAuthManager::RequestUnauthorized;
342 if (!accessTokenEndpoint.isValid()) {
343 qWarning() << "Endpoint for access token exchange is not valid. Cannot proceed.";
344 d->error = KQOAuthManager::RequestEndpointError;
348 d->error = KQOAuthManager::NoError;
350 d->opaqueRequest->clearRequest();
351 d->opaqueRequest->initRequest(KQOAuthRequest::AccessToken, accessTokenEndpoint);
352 d->opaqueRequest->setToken(d->requestToken);
353 d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
354 d->opaqueRequest->setVerifier(d->requestVerifier);
355 d->opaqueRequest->setConsumerKey(d->consumerKey);
356 d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
358 executeRequest(d->opaqueRequest);
361 void KQOAuthManager::sendAuthorizedRequest(QUrl requestEndpoint, const KQOAuthParameters &requestParameters) {
364 if (!d->isAuthorized) {
365 qWarning() << "No access tokens retrieved. Cannot send authorized requests.";
366 d->error = KQOAuthManager::RequestUnauthorized;
370 if (!requestEndpoint.isValid()) {
371 qWarning() << "Endpoint for authorized request is not valid. Cannot proceed.";
372 d->error = KQOAuthManager::RequestEndpointError;
376 d->error = KQOAuthManager::NoError;
378 d->opaqueRequest->clearRequest();
379 d->opaqueRequest->initRequest(KQOAuthRequest::AuthorizedRequest, requestEndpoint);
380 d->opaqueRequest->setAdditionalParameters(requestParameters);
381 d->opaqueRequest->setToken(d->requestToken);
382 d->opaqueRequest->setTokenSecret(d->requestTokenSecret);
383 d->opaqueRequest->setConsumerKey(d->consumerKey);
384 d->opaqueRequest->setConsumerSecretKey(d->consumerKeySecret);
386 executeRequest(d->opaqueRequest);
390 /////////////// Private slots //////////////////
392 void KQOAuthManager::onRequestReplyReceived( QNetworkReply *reply ) {
395 QNetworkReply::NetworkError networkError = reply->error();
396 switch (networkError) {
397 case QNetworkReply::NoError:
398 d->error = KQOAuthManager::NoError;
401 case QNetworkReply::ContentAccessDenied:
402 case QNetworkReply::AuthenticationRequiredError:
403 d->error = KQOAuthManager::RequestUnauthorized;
407 d->error = KQOAuthManager::NetworkError;
411 // Read the content of the reply from the network.
412 QByteArray networkReply = reply->readAll();
414 // Stop any timer we have set on the request.
415 d->r->requestTimerStop();
417 // Just don't do anything if we didn't get anything useful.
418 if(networkReply.isEmpty()) {
419 reply->deleteLater();
422 QMultiMap<QString, QString> responseTokens;
424 // We need to emit the signal even if we got an error.
425 if (d->error != KQOAuthManager::NoError) {
426 reply->deleteLater();
427 emit requestReady(networkReply);
432 responseTokens = d->createTokensFromResponse(networkReply);
433 d->opaqueRequest->clearRequest();
434 d->opaqueRequest->setHttpMethod(KQOAuthRequest::POST); // XXX FIXME: Convenient API does not support GET
435 if (!d->isAuthorized || !d->isVerified) {
436 if (d->setSuccessfulRequestToken(responseTokens)) {
437 qDebug() << "Successfully got request tokens.";
438 d->consumerKey = d->r->consumerKeyForManager();
439 d->consumerKeySecret = d->r->consumerKeySecretForManager();
440 d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
441 d->opaqueRequest->setCallbackUrl(d->r->callbackUrlForManager());
445 } else if (d->setSuccessfulAuthorized(responseTokens)) {
446 qDebug() << "Successfully got access tokens.";
447 d->opaqueRequest->setSignatureMethod(KQOAuthRequest::HMAC_SHA1);
450 } else if (d->currentRequestType == KQOAuthRequest::AuthorizedRequest) {
451 emit authorizedRequestDone();
455 emit requestReady(networkReply);
457 reply->deleteLater(); // We need to clean this up, after the event processing is done.
460 void KQOAuthManager::onVerificationReceived(QMultiMap<QString, QString> response) {
463 QString token = response.value("oauth_token");
464 QString verifier = response.value("oauth_verifier");
465 if (verifier.isEmpty()) {
466 d->error = KQOAuthManager::RequestUnauthorized;
469 verifier = QUrl::fromPercentEncoding(verifier.toUtf8()); // We get the raw URL response here so we need to convert it back
470 // to plain string so we can percent encode it again later in requests.
472 if (d->error == KQOAuthManager::NoError) {
473 d->requestVerifier = verifier;
474 d->isVerified = true;
477 emit authorizationReceived(token, verifier);
480 void KQOAuthManager::slotError(QNetworkReply::NetworkError error) {
484 d->error = KQOAuthManager::NetworkError;
485 QByteArray emptyResponse;
486 emit requestReady(emptyResponse);
487 emit authorizedRequestDone();
489 QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
490 reply->deleteLater();