Stylistic changes to improve readability of the code.
[accounts-sso:signon-oauth2-jam.git] / src / oauth2plugin.cpp
1 /*
2  * This file is part of oauth2 plugin
3  *
4  * Copyright (C) 2010 Nokia Corporation.
5  *
6  * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * version 2.1 as published by the Free Software Foundation.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  */
22
23 #include <QUrl>
24 #include <QNetworkAccessManager>
25 #include <QNetworkRequest>
26 #include <QNetworkProxy>
27 #include <QDateTime>
28 #include <QCryptographicHash>
29
30 #include "oauth2plugin.h"
31 #include "oauth2tokendata.h"
32
33 #ifdef TRACE
34 #undef TRACE
35 #endif
36 #define TRACE() qDebug() << __FILE__ << __LINE__ << __func__ << ":\t"
37
38 using namespace SignOn;
39
40 namespace OAuth2PluginNS {
41
42     // Enum for OAuth 1.0 POST request type
43     typedef enum {
44         OAUTH1_POST_REQUEST_INVALID = 0,
45         OAUTH1_POST_REQUEST_TOKEN,
46         OAUTH1_POST_ACCESS_TOKEN
47     } OAuth1RequestType;
48
49     const QString WEB_SERVER = QString("web_server");
50     const QString USER_AGENT = QString("user_agent");
51     const QString HMAC_SHA1 = QString("HMAC-SHA1");
52     const QString PLAINTEXT = QString("PLAINTEXT");
53     const QString RSA_SHA1 = QString("RSA-SHA1");
54
55     const int HTTP_STATUS_OK = 200;
56     const QString AUTH_CODE = QString("code");
57     const QString REDIRECT_URI = QString("redirect_uri");
58     const QString USERNAME = QString("username");
59     const QString PASSWORD = QString("password");
60     const QString ASSERTION_TYPE = QString("assertion_type");
61     const QString ASSERTION = QString("assertion");
62     const QString ACCESS_TOKEN = QString("access_token");
63     const QString EXPIRES_IN = QString("expires_in");
64     const QString GRANT_TYPE = QString("grant_type");
65     const QString AUTHORIZATION_CODE = QString("authorization_code");
66     const QString USER_BASIC = QString("user_basic");
67     const QString CLIENT_ID = QString("client_id");
68     const QString CLIENT_SECRET = QString("client_secret");
69     const QString REFRESH_TOKEN = QString("refresh_token");
70     const QString AUTH_ERROR = QString("error");
71     const QString USER_DENIED = QString("user_denied");
72
73     const QString EQUAL = QString("=");
74     const QString AMPERSAND = QString("&");
75     const QString EQUAL_WITH_QUOTE = QString("=\"");
76     const QString QUOTE = QString("\"");
77     const QString QUOTE_WITH_DELIMIT = QString("\",");
78     const QString SPACE = QString(" ");
79     const QString OAUTH = QString("OAuth");
80     const QString OAUTH_REALM = QString("realm");
81     const QString OAUTH_CALLBACK = QString("oauth_callback");
82     const QString OAUTH_CONSUMERKEY = QString("oauth_consumer_key");
83     const QString OAUTH_NONCE = QString("oauth_nonce");
84     const QString OAUTH_TIMESTAMP = QString("oauth_timestamp");
85     const QString OAUTH_SIGNATURE = QString("oauth_signature");
86     const QString OAUTH_SIGNATURE_METHOD  = QString("oauth_signature_method");
87     const QString OAUTH_VERSION = QString("oauth_version");
88     const QString OAUTH_VERSION_1 = QString("1.0");
89     const QString OAUTH_TOKEN = QString("oauth_token");
90     const QString OAUTH_TOKEN_SECRET = QString("oauth_token_secret");
91     const QString OAUTH_VERIFIER = QString("oauth_verifier");
92     const QString OAUTH_PROBLEM = QString("oauth_problem");
93     const QString OAUTH_USER_REFUSED = QString("user_refused");
94     const QString OAUTH_PERMISSION_DENIED = QString("permission_denied");
95
96     const QByteArray CONTENT_TYPE = QByteArray("Content-Type");
97     const QByteArray CONTENT_APP_URLENCODED = QByteArray("application/x-www-form-urlencoded");
98     const QByteArray CONTENT_APP_JSON = QByteArray("application/json");
99     const QByteArray CONTENT_TEXT_PLAIN = QByteArray("text/plain");
100
101
102     class OAuth2Plugin::Private
103     {
104     public:
105         Private(OAuth2Plugin* parent) : m_parent(parent),
106             m_manager(0), m_reply(0)
107         {
108             TRACE();
109             m_networkProxy = QNetworkProxy::applicationProxy();
110             m_oauth1Token.clear();
111             m_oauth1TokenSecret.clear();
112             m_oauth1TokenVerifier.clear();
113             m_oauth1RequestType = OAUTH1_POST_REQUEST_INVALID;
114         }
115
116         ~Private()
117         {
118             TRACE();
119             if (m_reply)
120                 m_reply->deleteLater();
121             if (m_manager)
122                 m_manager->deleteLater();
123         }
124
125         OAuth2Plugin *m_parent;
126         QNetworkAccessManager *m_manager;
127         QNetworkProxy m_networkProxy;
128         QNetworkReply *m_reply;
129         QString m_mechanism;
130         OAuth2PluginData m_oauth2Data;
131         OAuth1PluginData m_oauth1Data;
132         QByteArray m_oauth1Token;
133         QByteArray m_oauth1TokenSecret;
134         QString m_oauth1TokenVerifier;
135         OAuth1RequestType m_oauth1RequestType;
136         QVariantMap m_tokens;
137         QString m_key;
138
139     public:
140         //convert network error into auth plugin error code
141         Error ssoErrorCode(QNetworkReply::NetworkError networkError)
142         {
143             //first specific cases
144             if (networkError == QNetworkReply::NoError)
145                 return 0;
146             if (networkError == QNetworkReply::SslHandshakeFailedError)
147                 return Error::Ssl;
148             if (networkError <= QNetworkReply::UnknownNetworkError)
149                 return Error::NoConnection;
150             //content errors handled by http status
151             if ((networkError > QNetworkReply::UnknownProxyError)
152                 && (networkError <= QNetworkReply::UnknownContentError))
153                 return 0;
154             //rest
155             return Error::Network;
156         }
157     }; //Private
158
159     OAuth2Plugin::OAuth2Plugin(QObject *parent)
160             : AuthPluginInterface(parent), d(new Private(this))
161     {
162         TRACE();
163     }
164
165     OAuth2Plugin::~OAuth2Plugin()
166     {
167         TRACE();
168         delete d;
169         d = 0;
170     }
171
172     QString OAuth2Plugin::type() const
173     {
174         TRACE();
175         return QString("oauth2");
176     }
177
178     QStringList OAuth2Plugin::mechanisms() const
179     {
180         TRACE();
181         QStringList res = QStringList();
182         res.append(WEB_SERVER);
183         res.append(USER_AGENT);
184         res.append(HMAC_SHA1);
185         res.append(PLAINTEXT);
186         return res;
187     }
188
189     void OAuth2Plugin::cancel()
190     {
191         TRACE();
192         if (d->m_reply)
193             d->m_reply->abort();
194     }
195
196     void OAuth2Plugin::sendAuthRequest()
197     {
198         QUrl url(QString("https://%1/%2").arg(d->m_oauth2Data.Host()).arg(d->m_oauth2Data.AuthPath()));
199         url.addQueryItem(CLIENT_ID, d->m_oauth2Data.ClientId());
200         url.addQueryItem(REDIRECT_URI, d->m_oauth2Data.RedirectUri());
201         url.addQueryItem(QString("display"), QString("touch"));
202         url.addQueryItem(QString("type"), d->m_mechanism);
203         if (!d->m_oauth2Data.Scope().empty()) {
204             // Passing comma de-limited list of scope
205             url.addQueryItem(QString("scope"), d->m_oauth2Data.Scope().join(","));
206         }
207         TRACE() << "Url = " << url.toString();
208         SignOn::UiSessionData uiSession;
209         uiSession.setOpenUrl(url.toString());
210         emit userActionRequired(uiSession);
211     }
212
213     bool OAuth2Plugin::validateInput(const SignOn::SessionData &inData, const QString &mechanism)
214     {
215         if ((mechanism == WEB_SERVER) || (mechanism == USER_AGENT)) {
216             OAuth2PluginData input = inData.data<OAuth2PluginData>();
217             if (input.Host().isEmpty()
218                 || input.ClientId().isEmpty()
219                 || input.ClientSecret().isEmpty()
220                 || input.RedirectUri().isEmpty()
221                 || input.AuthPath().isEmpty()
222                 || ((mechanism == WEB_SERVER)
223                     && (input.TokenPath().isEmpty()))) {
224                 return false;
225             }
226         }
227         else {
228             OAuth1PluginData input = inData.data<OAuth1PluginData>();
229             if (input.AuthorizationEndpoint().isEmpty()
230                 || input.ConsumerKey().isEmpty()
231                 || input.ConsumerSecret().isEmpty()
232                 || input.Callback().isEmpty()
233                 || input.TokenEndpoint().isEmpty()
234                 || input.RequestEndpoint().isEmpty()){
235                 return false;
236             }
237         }
238         return true;
239     }
240
241     void OAuth2Plugin::process(const SignOn::SessionData &inData,
242                                const QString &mechanism)
243     {
244         TRACE();
245         OAuth2PluginData input;
246
247         if ((!mechanism.isEmpty()) && (!mechanisms().contains(mechanism))) {
248             emit error(Error(Error::MechanismNotAvailable));
249             return;
250         }
251
252         if (!validateInput(inData, mechanism)) {
253             TRACE() << "Invalid parameters passed";
254             emit error(Error(Error::InvalidQuery));
255             return;
256         }
257
258         QString proxy = inData.NetworkProxy();
259         //set proxy from params
260         if (!proxy.isEmpty()) {
261             QUrl proxyUrl(proxy);
262             if (!proxyUrl.host().isEmpty()) {
263                 d->m_networkProxy = QNetworkProxy(
264                         QNetworkProxy::HttpProxy,
265                         proxyUrl.host(),
266                         proxyUrl.port(),
267                         proxyUrl.userName(),
268                         proxyUrl.password());
269                 TRACE() << proxyUrl.host() << ":" <<  proxyUrl.port();
270             }
271         } else {
272             d->m_networkProxy = QNetworkProxy::applicationProxy();
273         }
274
275         d->m_mechanism = mechanism;
276         if (mechanism == WEB_SERVER || mechanism == USER_AGENT) {
277             OAuth2PluginData data = inData.data<OAuth2PluginData>();
278             d->m_key = data.ClientId();
279         } else {
280             OAuth1PluginData data = inData.data<OAuth1PluginData>();
281             d->m_key = data.ConsumerKey();
282         }
283         //get stored data
284         OAuth2TokenData tokens = inData.data<OAuth2TokenData>();
285         d->m_tokens = tokens.Tokens();
286         if (inData.UiPolicy() == RequestPasswordPolicy) {
287             //remove old token for given Key
288             TRACE() << d->m_tokens;
289             d->m_tokens.remove(d->m_key);
290             OAuth2TokenData tokens;
291             tokens.setTokens(d->m_tokens);
292             emit store(tokens);
293             TRACE() << d->m_tokens;
294         }
295
296         QVariant tokenVar = d->m_tokens.value(d->m_key);
297         QVariantMap token;
298         if (tokenVar.canConvert<QVariantMap>())
299             token = tokenVar.value<QVariantMap>();
300         //return stored session if it is valid
301         if (token.value("Expiry").toUInt() > QDateTime::currentDateTime().toTime_t()) {
302             if (mechanism == WEB_SERVER || mechanism == USER_AGENT) {
303                 OAuth2PluginTokenData response;
304                 response.setAccessToken(token.value("Token").toByteArray());
305                 response.setRefreshToken(token.value("Token2").toString());
306                 response.setExpiresIn(token.value("Expiry").toUInt());
307                 emit result(response);
308                 return;
309             } else {
310                 OAuth1PluginTokenData response;
311                 response.setAccessToken(token.value("Token").toByteArray());
312                 emit result(response);
313                 return;
314             }
315         }
316         if (mechanism == WEB_SERVER || mechanism == USER_AGENT) {
317             d->m_oauth2Data = inData.data<OAuth2PluginData>();
318             sendAuthRequest();
319         }
320         else if (mechanism == HMAC_SHA1 ||mechanism == PLAINTEXT) {
321             d->m_oauth1Data = inData.data<OAuth1PluginData>();
322             d->m_oauth1RequestType = OAUTH1_POST_REQUEST_TOKEN;
323             sendOAuth1PostRequest();
324         }
325         else {
326             emit error(Error(Error::MechanismNotAvailable));
327         }
328     }
329
330     QString OAuth2Plugin::urlEncode(QString strData)
331     {
332         return QUrl::toPercentEncoding(strData).constData();
333     }
334
335     // Create a HMAC-SHA1 signature
336     QByteArray OAuth2Plugin::createHMACSha1(const QByteArray &baseSignatureString , const QByteArray &secret)
337     {
338         // The algorithm is defined in RFC 2104
339         int blockSize = 64;
340         QByteArray key(baseSignatureString);
341         QByteArray opad(blockSize, 0x5c);
342         QByteArray ipad(blockSize, 0x36);
343
344         // If key size is too large, compute the hash
345         if (key.size() > blockSize) {
346             key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
347         }
348
349         // If too small, pad with 0x00
350         if (key.size() < blockSize) {
351             key += QByteArray(blockSize - key.size(), 0x00);
352         }
353
354         // Compute the XOR operations
355         for (int i=0; i <= key.size() - 1; i++) {
356             ipad[i] = (char) (ipad[i] ^ key[i]);
357             opad[i] = (char) (opad[i] ^ key[i]);
358         }
359
360         // Append the data to ipad
361         ipad += secret;
362
363         // Hash sha1 of ipad and append the data to opad
364         opad += QCryptographicHash::hash(ipad, QCryptographicHash::Sha1);
365
366         // Return array contains the result of HMACSha1
367         return QCryptographicHash::hash(opad, QCryptographicHash::Sha1);
368     }
369
370     QByteArray OAuth2Plugin::constructSignatureBaseString(const QString &aUrl, const QString &callback,
371                                                           const OAuth1PluginData &inData, const QString &timestamp,
372                                                           const QString &nonce)
373     {
374         QMap <QString, QString> oAuthHeaderList;
375         QUrl fullUrl(aUrl);
376
377         // Constructing the base string as per RFC 5849. Sec 3.4.1
378         QList< QPair <QString, QString> > urlParameterList = fullUrl.queryItems();
379         QPair<QString, QString> tempPair;
380         foreach (tempPair, urlParameterList) {
381             oAuthHeaderList[tempPair.first] = tempPair.second;
382         }
383
384         if (!callback.isEmpty()) {
385             oAuthHeaderList[OAUTH_CALLBACK] = callback;
386         }
387         oAuthHeaderList[OAUTH_CONSUMERKEY]  = inData.ConsumerKey();
388         oAuthHeaderList[OAUTH_NONCE] = nonce;
389         oAuthHeaderList[OAUTH_SIGNATURE_METHOD] = d->m_mechanism;
390         oAuthHeaderList[OAUTH_TIMESTAMP] = timestamp;
391         if (!d->m_oauth1Token.isEmpty()) {
392             oAuthHeaderList[OAUTH_TOKEN] = d->m_oauth1Token;
393         }
394         if (!d->m_oauth1TokenVerifier.isEmpty()) {
395             oAuthHeaderList[OAUTH_VERIFIER] = d->m_oauth1TokenVerifier;
396         }
397         oAuthHeaderList[OAUTH_VERSION] =OAUTH_VERSION_1;
398         QString oAuthHeaderString;
399         foreach (QString key , oAuthHeaderList.keys()) {
400             oAuthHeaderString.append(urlEncode(key) + EQUAL
401                                      + urlEncode(oAuthHeaderList[key]) + AMPERSAND);
402         }
403         //truncation is required to remove the unneccessary trailing '&'
404         oAuthHeaderString.truncate(oAuthHeaderString.length() - AMPERSAND.length());
405         QString urlWithHostAndPath = fullUrl.toString(QUrl::RemoveUserInfo | QUrl::RemoveQuery
406                                                       | QUrl::RemoveFragment | QUrl::StripTrailingSlash);
407
408         QByteArray signatureBase;
409         signatureBase.append("POST");
410         signatureBase.append(AMPERSAND);
411         signatureBase.append(urlEncode(urlWithHostAndPath));
412         signatureBase.append(AMPERSAND);
413         signatureBase.append(urlEncode(oAuthHeaderString));
414         return signatureBase;
415     }
416
417     // Function  to create the Authorization header
418     QString OAuth2Plugin::createOAuthHeader(const QString &aUrl, OAuth1PluginData inData,
419                                             const QString &callback)
420     {
421         // Nonce
422         unsigned long nonce1 = (unsigned long) random();
423         unsigned long nonce2 = (unsigned long) random();
424         QString oauthNonce = QString("%1%2").arg(nonce1).arg(nonce2);
425
426         // Timestamp
427         QString oauthTimestamp = QString("%1").arg(QDateTime::currentDateTime().toTime_t());
428
429         QString authHeader = OAUTH + SPACE;
430         if (!inData.Realm().isEmpty()) {
431             authHeader.append(OAUTH_REALM + EQUAL_WITH_QUOTE
432                               + urlEncode(inData.Realm()) + QUOTE_WITH_DELIMIT);
433         }
434         if (!callback.isEmpty()) {
435             authHeader.append(OAUTH_CALLBACK + EQUAL_WITH_QUOTE
436                               + urlEncode(callback) + QUOTE_WITH_DELIMIT);
437         }
438         authHeader.append(OAUTH_CONSUMERKEY + EQUAL_WITH_QUOTE
439                           + urlEncode(inData.ConsumerKey()) + QUOTE_WITH_DELIMIT);
440         authHeader.append(OAUTH_NONCE + EQUAL_WITH_QUOTE
441                           + urlEncode(oauthNonce) + QUOTE_WITH_DELIMIT );
442         authHeader.append(OAUTH_TIMESTAMP + EQUAL_WITH_QUOTE
443                           + urlEncode(oauthTimestamp) + QUOTE_WITH_DELIMIT);
444         if (!d->m_oauth1Token.isEmpty()) {
445             authHeader.append(OAUTH_TOKEN + EQUAL_WITH_QUOTE
446                               + urlEncode(d->m_oauth1Token) + QUOTE_WITH_DELIMIT);
447         }
448
449         // Creating the signature
450         // TODO: RSA-SHA1 signature method should be implemented
451         // PLAINTEXT signature method
452         if (d->m_mechanism == PLAINTEXT) {
453             QByteArray signature;
454             signature.append(urlEncode(inData.ConsumerSecret()));
455             signature.append(AMPERSAND);
456             signature.append(urlEncode(d->m_oauth1TokenSecret));
457             TRACE() << "Signature = " << signature;
458             authHeader.append(OAUTH_SIGNATURE + EQUAL_WITH_QUOTE
459                               + urlEncode(signature) + QUOTE_WITH_DELIMIT);
460         }
461         else if (d->m_mechanism == HMAC_SHA1) { // HMAC-SHA1 signature method
462             QByteArray signatureBase = constructSignatureBaseString(aUrl, callback, inData,
463                                                                     oauthTimestamp, oauthNonce);
464             TRACE() << "Base String = " << signatureBase;
465             QByteArray signature;
466             QByteArray secretKey;
467             secretKey.append(urlEncode(inData.ConsumerSecret()));
468             secretKey.append(AMPERSAND);
469             secretKey.append(urlEncode(d->m_oauth1TokenSecret));
470             signature = createHMACSha1(secretKey, signatureBase);
471             TRACE() << "Signature = " << signature;
472             authHeader.append(OAUTH_SIGNATURE + EQUAL_WITH_QUOTE
473                               + urlEncode(signature.toBase64()) + QUOTE_WITH_DELIMIT);
474         }
475         else {
476             Q_ASSERT_X(false, "createOAuthHeader", "Unsupported mechanism");
477         }
478
479         authHeader.append(OAUTH_SIGNATURE_METHOD + EQUAL_WITH_QUOTE
480                           + urlEncode(d->m_mechanism) + QUOTE_WITH_DELIMIT );
481         if (!d->m_oauth1TokenVerifier.isEmpty()) {
482             authHeader.append(OAUTH_VERIFIER + EQUAL_WITH_QUOTE
483                               + urlEncode(d->m_oauth1TokenVerifier) + QUOTE_WITH_DELIMIT);
484         }
485         authHeader.append( OAUTH_VERSION + EQUAL_WITH_QUOTE
486                            + urlEncode(OAUTH_VERSION_1) + QUOTE);
487
488         return authHeader;
489     }
490
491     void OAuth2Plugin::userActionFinished(const SignOn::UiSessionData &data)
492     {
493         TRACE();
494
495         if (data.QueryErrorCode() != QUERY_ERROR_NONE) {
496             TRACE() << "userActionFinished with error: " << data.QueryErrorCode();
497             if (data.QueryErrorCode() == QUERY_ERROR_CANCELED)
498                 emit error(Error(Error::SessionCanceled, QLatin1String("Cancelled by user")));
499             else
500                 emit error(Error(Error::UserInteraction,
501                                  QString("userActionFinished error: ")
502                                  + QString::number(data.QueryErrorCode())));
503             return;
504         }
505
506         TRACE() << data.UrlResponse();
507
508         // Checking if authorization server granted access
509         QUrl url = QUrl(data.UrlResponse());
510         if (url.hasQueryItem(AUTH_ERROR)) {
511             TRACE() << "Server denied access permission";
512             emit error(Error(Error::PermissionDenied, url.queryItemValue(AUTH_ERROR)));
513             return;
514         }
515
516         if (d->m_mechanism == USER_AGENT) {
517             // Response should contain the access token
518             OAuth2PluginTokenData respData;
519             QUrl url = QUrl(data.UrlResponse());
520             QString fragment;
521             if (url.hasFragment()) {
522                 fragment = url.fragment ();
523                 QStringList list;
524                 list = fragment.split(QRegExp("&|="), QString::SkipEmptyParts);
525                 int i =0;
526                 while (i < list.count()) {
527                     if (list.at(i) == ACCESS_TOKEN) {
528                         respData.setAccessToken(list.at(i + 1));
529                     }
530                     else if (list.at(i) == EXPIRES_IN) {
531                         respData.setExpiresIn(QString(list.at(i + 1)).toInt());
532                     }
533                     else if (list.at(i) == REFRESH_TOKEN) {
534                         respData.setRefreshToken(list.at(i + 1));
535                     }
536                     i+=2;
537                 }
538                 if (respData.AccessToken().isEmpty()) {
539                     emit error(Error(Error::Unknown, QString("Access token not present")));
540                 } else {
541                     //store session key for later use
542                     OAuth2TokenData tokens;
543                     QVariantMap token;
544                     token.insert("Token", respData.AccessToken());
545                     token.insert("Token2", respData.RefreshToken());
546                     token.insert("Expiry", respData.ExpiresIn());
547                     d->m_tokens.insert(d->m_key, QVariant::fromValue(token));
548                     tokens.setTokens(d->m_tokens);
549                     emit store(tokens);
550                     TRACE() << d->m_tokens;
551
552                     emit result(respData);
553                 }
554             }
555             else {
556                 emit error(Error(Error::Unknown, QString("Access token not present")));
557             }
558         } else if (d->m_mechanism == WEB_SERVER) {
559             // Access grant can be one of the floolwing types
560             // 1. Authorization code (code, redirect_uri)
561             // 2. Resource owner credentials (username, password)
562             // 3. Assertion (assertion_type, assertion)
563             // 4. Refresh Token (refresh_token)
564             if (url.hasQueryItem(AUTH_CODE)) {
565                 sendPostRequest(getQueryString(getAuthCodeRequestParams(url.queryItemValue(AUTH_CODE))));
566             }
567             else if (url.hasQueryItem(USERNAME) && url.hasQueryItem(PASSWORD)) {
568                 sendPostRequest(getQueryString(getUserBasicRequestParams(url.queryItemValue(USERNAME),
569                                                                          url.queryItemValue(PASSWORD))));
570             }
571             else if (url.hasQueryItem(ASSERTION_TYPE) && url.hasQueryItem(ASSERTION)) {
572                 sendPostRequest(getQueryString(getAssertionRequestParams(url.queryItemValue(ASSERTION_TYPE),
573                                                                          url.queryItemValue(ASSERTION))));
574             }
575             else if (url.hasQueryItem(REFRESH_TOKEN)) {
576                 sendPostRequest(getQueryString(getRefreshTokenRequestParams(url.queryItemValue(REFRESH_TOKEN))));
577             }
578             else {
579                 emit error(Error(Error::Unknown, QString("Access grant not present")));
580             }
581         }
582         else { // For all OAuth 1 mechanisms
583             if (url.hasQueryItem(OAUTH_VERIFIER)) {
584                 d->m_oauth1TokenVerifier = url.queryItemValue(OAUTH_VERIFIER);
585                 d->m_oauth1Data.setCallback(QString());
586                 d->m_oauth1RequestType = OAUTH1_POST_ACCESS_TOKEN;
587                 sendOAuth1PostRequest();
588             }
589             else if (url.hasQueryItem(OAUTH_PROBLEM)) {
590                 handleOAuth1Error(url.queryItemValue(OAUTH_PROBLEM).toAscii());
591             }
592             else {
593                 emit error(Error(Error::Unknown, QString("oauth_verifier missing")));
594             }
595         }
596     }
597
598     void OAuth2Plugin::handleRequestFinishedError(QNetworkReply *reply, const QByteArray &replyContent)
599     {
600         TRACE() << QString("http_error received : %1, %2").arg(reply->error()).arg(reply->errorString());
601         Error err = d->ssoErrorCode(reply->error());
602         TRACE() << "Error type = " << err.type();
603         if (err.type() == Error::Ssl) {
604             return; //this is handled with sslError signal
605         }
606         else if (err.type()) {
607             err.setMessage(reply->errorString());
608             emit error(err);
609         }
610         else {
611             handleError(replyContent);
612         }
613     }
614
615     // Function to handle responses for OAuth 2.0 requests
616     void OAuth2Plugin::replyFinished(QNetworkReply *reply)
617     {
618         TRACE()<< "Finished signal received";
619         QByteArray replyContent = reply->readAll();
620         TRACE() << replyContent;
621         if (reply->error() != QNetworkReply::NoError) {
622             handleRequestFinishedError(reply, replyContent);
623             return;
624         }
625
626         // Handle error responses
627         QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
628         TRACE() << statusCode;
629         if (statusCode != HTTP_STATUS_OK) {
630             handleError(replyContent);
631             return;
632         }
633
634         // Handling 200 OK response (HTTP_STATUS_OK) WITH content
635         if (reply->hasRawHeader(CONTENT_TYPE)) {
636
637             // Handling application/json content type
638             if (reply->rawHeader(CONTENT_TYPE).startsWith(CONTENT_APP_JSON)) {
639                 TRACE()<< "application/json content received";
640                 QByteArray accessToken = parseReply(replyContent, QByteArray("\"access_token\""));
641                 QByteArray expiresIn = parseReply(replyContent, QByteArray("\"expires_in\""));
642                 QByteArray refreshToken = parseReply(replyContent, QByteArray("\"refresh_token\""));
643
644                 if (accessToken.isEmpty()) {
645                     TRACE()<< "Access token is empty";
646                     emit error(Error(Error::Unknown));
647                 }
648                 else {
649                     OAuth2PluginTokenData response;
650                     response.setAccessToken(accessToken);
651                     response.setRefreshToken(refreshToken);
652                     response.setExpiresIn(expiresIn.toInt());
653                     emit result(response);
654                 }
655             }
656             // Added to test with facebook Graph API's (handling text/plain content type)
657             else if (reply->rawHeader(CONTENT_TYPE).startsWith(CONTENT_TEXT_PLAIN)){
658                 TRACE()<< "text/plain; charset=UTF-8 content received";
659                 QByteArray accessToken = parsePlainTextReply(replyContent, QByteArray("access_token"));
660                 QByteArray expiresIn = parsePlainTextReply(replyContent, QByteArray("expires"));
661                 QByteArray refreshToken = parsePlainTextReply(replyContent, QByteArray("refresh_token"));
662
663                 if (accessToken.isEmpty()) {
664                     TRACE()<< "Access token is empty";
665                     emit error(Error(Error::Unknown));
666                 }
667                 else {
668                     OAuth2PluginTokenData response;
669                     response.setAccessToken(accessToken);
670                     response.setRefreshToken(refreshToken);
671                     response.setExpiresIn(expiresIn.toInt());
672                     emit result(response);
673                 }
674             }
675             else {
676                 TRACE()<< "Unsupported content type received: " << reply->rawHeader(CONTENT_TYPE);
677                 emit error(Error(Error::OperationFailed, QString("Unsupported content type received")));
678             }
679         }
680         // Handling 200 OK response (HTTP_STATUS_OK) WITHOUT content
681         else {
682             TRACE()<< "Content is not present";
683             emit error(Error(Error::OperationFailed, QString("Content missing")));
684         }
685     }
686
687     // Function to handle responses for OAuth 1.0a Request token request
688     void OAuth2Plugin::replyOAuth1RequestFinished(QNetworkReply *reply)
689     {
690         TRACE()<< "Finished signal received";
691         QByteArray replyContent = reply->readAll();
692         TRACE() << replyContent;
693         if (reply->error() != QNetworkReply::NoError) {
694             handleRequestFinishedError(reply, replyContent);
695             d->m_oauth1RequestType = OAUTH1_POST_REQUEST_INVALID;
696             return;
697         }
698
699         // Handle error responses
700         QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
701         TRACE() << statusCode;
702         if (statusCode != HTTP_STATUS_OK) {
703             handleError(replyContent);
704             d->m_oauth1RequestType = OAUTH1_POST_REQUEST_INVALID;
705             return;
706         }
707
708         // Handling 200 OK response (HTTP_STATUS_OK) WITH content
709         if (reply->hasRawHeader(CONTENT_TYPE)) {
710
711             // Checking if supported content type received
712             if ((reply->rawHeader(CONTENT_TYPE).startsWith(CONTENT_APP_URLENCODED))
713                 || (reply->rawHeader(CONTENT_TYPE).startsWith(CONTENT_TEXT_PLAIN))) {
714
715                 if (d->m_oauth1RequestType == OAUTH1_POST_REQUEST_TOKEN) {
716                     // Extracting the request token, token secret
717                     d->m_oauth1Token = parsePlainTextReply(replyContent, OAUTH_TOKEN.toAscii());
718                     d->m_oauth1TokenSecret = parsePlainTextReply(replyContent, OAUTH_TOKEN_SECRET.toAscii());
719                     if (d->m_oauth1Token.isEmpty() || d->m_oauth1TokenSecret.isEmpty()) {
720                         TRACE() << "OAuth token is empty";
721                         emit error(Error(Error::Unknown, QString("Request token missing")));
722                     }
723                     else {
724                         SignOn::UiSessionData uiSession;
725                         QUrl url(d->m_oauth1Data.AuthorizationEndpoint());
726                         url.addQueryItem(OAUTH_TOKEN, d->m_oauth1Token);
727                         uiSession.setOpenUrl(url.toString());
728                         TRACE() << "URL = " << url.toString();
729                         emit userActionRequired(uiSession);
730                         return;
731                     }
732                 }
733                 else if (d->m_oauth1RequestType == OAUTH1_POST_ACCESS_TOKEN) {
734                     // Extracting the access token
735                     d->m_oauth1Token = parsePlainTextReply(replyContent, OAUTH_TOKEN.toAscii());
736                     if (d->m_oauth1Token.isEmpty()) {
737                         TRACE()<< "OAuth token is empty";
738                         emit error(Error(Error::Unknown, QString("Access token missing")));
739                     }
740                     else {
741                         OAuth1PluginTokenData response;
742                         response.setAccessToken(d->m_oauth1Token);
743                         emit result(response);
744                     }
745                 }
746                 else {
747                     Q_ASSERT_X(false, "sendOAuth1PostRequest", "Invalid OAuth1 POST request");
748                 }
749             }
750             else {
751                 TRACE()<< "Unsupported content type received: " << reply->rawHeader(CONTENT_TYPE);
752                 emit error(Error(Error::OperationFailed,
753                                  QString("Unsupported content type received")));
754             }
755             d->m_oauth1RequestType = OAUTH1_POST_REQUEST_INVALID;
756         }
757         // Handling 200 OK response (HTTP_STATUS_OK) WITHOUT content
758         else {
759             TRACE()<< "Content is not present";
760             emit error(Error(Error::OperationFailed, QString("Content missing")));
761             d->m_oauth1RequestType = OAUTH1_POST_REQUEST_INVALID;
762         }
763     }
764
765     void OAuth2Plugin::handleOAuth1Error(const QByteArray &errorString)
766     {
767         Error::ErrorType type = Error::Unknown;
768         if (errorString == OAUTH_USER_REFUSED || errorString == OAUTH_PERMISSION_DENIED) {
769             type = Error::PermissionDenied;
770         }
771         TRACE() << "Error Emitted";
772         emit error(Error(type, errorString));
773     }
774
775     void OAuth2Plugin::handleError(const QByteArray &reply)
776     {
777         TRACE();
778         QByteArray errorString = parseReply(reply, QByteArray("\"error\""));
779         if (!errorString.isEmpty()) {
780             Error::ErrorType type = Error::Unknown;
781             if (errorString == QByteArray("incorrect_client_credentials")) {
782                 type = Error::InvalidCredentials;
783             }
784             else if (errorString == QByteArray("redirect_uri_mismatch")) {
785                 type = Error::InvalidCredentials;
786             }
787             else if (errorString == QByteArray("bad_authorization_code")) {
788                 type = Error::InvalidCredentials;
789             }
790             else if (errorString == QByteArray("invalid_client_credentials")) {
791                 type = Error::InvalidCredentials;
792             }
793             else if (errorString == QByteArray("unauthorized_client")) {
794                 type = Error::NotAuthorized;
795             }
796             else if (errorString == QByteArray("invalid_assertion")) {
797                 type = Error::InvalidCredentials;
798             }
799             else if (errorString == QByteArray("unknown_format")) {
800                 type = Error::InvalidQuery;
801             }
802             else if (errorString == QByteArray("authorization_expired")) {
803                 type = Error::InvalidCredentials;
804             }
805             else if (errorString == QByteArray("multiple_credentials")) {
806                 type = Error::InvalidQuery;
807             }
808             else if (errorString == QByteArray("invalid_user_credentials")) {
809                 type = Error::InvalidCredentials;
810             }
811             TRACE() << "Error Emitted";
812             emit error(Error(type, errorString));
813             return;
814         }
815
816         // Added to work with facebook Graph API's
817         errorString = parseReply(reply, QByteArray("\"message\""));
818         if (!errorString.isEmpty()) {
819             TRACE() << "Error Emitted";
820             emit error(Error(Error::OperationFailed, errorString));
821             return;
822         }
823
824         errorString = parsePlainTextReply(reply, OAUTH_PROBLEM.toAscii());
825         if (!errorString.isEmpty()) {
826             handleOAuth1Error(errorString);
827             return;
828         }
829
830         TRACE() << "Error Emitted";
831         emit error(Error(Error::Unknown, errorString));
832     }
833
834     void OAuth2Plugin::slotError(QNetworkReply::NetworkError err)
835     {
836         TRACE()<< "error signal received:" << err;
837     }
838
839     void OAuth2Plugin::slotSslErrors(QList<QSslError> errorList)
840     {
841         TRACE() << "Error: " << errorList;
842         QString errorString;
843         for (int i = 0; i < errorList.size(); ++i) {
844             if (i!=0)
845                 errorString += QString(";");
846             errorString +=errorList.at(i).errorString();
847         }
848         emit error(Error(Error::Ssl, errorString));
849     }
850
851     void OAuth2Plugin::refresh(const SignOn::UiSessionData &data)
852     {
853         TRACE();
854         emit refreshed(data);
855     }
856
857     const QVariantMap OAuth2Plugin::getAuthCodeRequestParams(QString code)
858     {
859         QVariantMap authHeaders;
860         authHeaders.insert(GRANT_TYPE, AUTHORIZATION_CODE);
861         authHeaders.insert(CLIENT_ID, d->m_oauth2Data.ClientId());
862         authHeaders.insert(CLIENT_SECRET, d->m_oauth2Data.ClientSecret());
863         authHeaders.insert(AUTH_CODE, code);
864         authHeaders.insert(REDIRECT_URI, QUrl(d->m_oauth2Data.RedirectUri()).toEncoded());
865         return authHeaders;
866     }
867
868     const QVariantMap OAuth2Plugin::getUserBasicRequestParams(QString username, QString password)
869     {
870         QVariantMap authHeaders;
871         authHeaders.insert(GRANT_TYPE, USER_BASIC);
872         authHeaders.insert(CLIENT_ID, d->m_oauth2Data.ClientId());
873         authHeaders.insert(CLIENT_SECRET, d->m_oauth2Data.ClientSecret());
874         authHeaders.insert(USERNAME, username);
875         authHeaders.insert(PASSWORD, password);
876         return authHeaders;
877     }
878
879     const QVariantMap OAuth2Plugin::getAssertionRequestParams(QString assertion_type, QString assertion)
880     {
881         QVariantMap authHeaders;
882         authHeaders.insert(GRANT_TYPE, ASSERTION);
883         authHeaders.insert(CLIENT_ID, d->m_oauth2Data.ClientId());
884         authHeaders.insert(CLIENT_SECRET, d->m_oauth2Data.ClientSecret());
885         authHeaders.insert(ASSERTION_TYPE, assertion_type);
886         authHeaders.insert(ASSERTION, assertion);
887         return authHeaders;
888     }
889
890     const QVariantMap OAuth2Plugin::getRefreshTokenRequestParams(QString refresh_token)
891     {
892         QVariantMap authHeaders;
893         authHeaders.insert(GRANT_TYPE, REFRESH_TOKEN);
894         authHeaders.insert(CLIENT_ID, d->m_oauth2Data.ClientId());
895         authHeaders.insert(CLIENT_SECRET, d->m_oauth2Data.ClientSecret());
896         authHeaders.insert(REFRESH_TOKEN, refresh_token);
897         return authHeaders;
898     }
899
900     QByteArray OAuth2Plugin::getQueryString(const QVariantMap& parameters)
901     {
902         QUrl url;
903         QMapIterator<QString,QVariant> i(parameters);
904         while (i.hasNext()) {
905             i.next();
906             if (i.key() == AUTH_CODE) {
907                 url.addEncodedQueryItem(i.key().toAscii(), i.value().toByteArray());
908             }
909             else {
910                 url.addQueryItem(i.key(),i.value().toString());
911             }
912         }
913         return url.encodedQuery();
914     }
915
916     // Method to send OAuth 2.0 POST request
917     void OAuth2Plugin::sendPostRequest(const QByteArray &queryString)
918     {
919         TRACE();
920
921         if (!d->m_manager) {
922             d->m_manager = new QNetworkAccessManager();
923             d->m_manager->disconnect();
924             d->m_manager->setProxy(d->m_networkProxy);
925         }
926
927         QUrl url(QString("https://%1/%2").arg(d->m_oauth2Data.Host())
928                  .arg(d->m_oauth2Data.TokenPath()));
929         QNetworkRequest request(url);
930         request.setRawHeader(CONTENT_TYPE, CONTENT_APP_URLENCODED);
931         connect(d->m_manager, SIGNAL(finished(QNetworkReply*)),
932                 this, SLOT(replyFinished(QNetworkReply*)));
933
934         TRACE() << "Query string = " << queryString;
935         d->m_reply = d->m_manager->post(request, queryString);
936
937         connect(d->m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
938                 this, SLOT(slotError(QNetworkReply::NetworkError)));
939         connect(d->m_reply, SIGNAL(sslErrors(QList<QSslError>)),
940                 this, SLOT(slotSslErrors(QList<QSslError>)));
941     }
942
943     // Function to send OAuth 1.0a POST requests
944     void OAuth2Plugin::sendOAuth1PostRequest()
945     {
946         TRACE();
947
948         if (!d->m_manager) {
949             d->m_manager = new QNetworkAccessManager();
950             d->m_manager->disconnect();
951             d->m_manager->setProxy(d->m_networkProxy);
952         }
953
954         QNetworkRequest request;
955         request.setRawHeader(CONTENT_TYPE, CONTENT_APP_URLENCODED);
956         QString authHeader;
957         if (d->m_oauth1RequestType == OAUTH1_POST_REQUEST_TOKEN) {
958             request.setUrl(d->m_oauth1Data.RequestEndpoint());
959             authHeader = createOAuthHeader(d->m_oauth1Data.RequestEndpoint(),
960                                            d->m_oauth1Data,
961                                            d->m_oauth1Data.Callback());
962             connect(d->m_manager, SIGNAL(finished(QNetworkReply*)),
963                     this, SLOT(replyOAuth1RequestFinished(QNetworkReply*)));
964         }
965         else if (d->m_oauth1RequestType == OAUTH1_POST_ACCESS_TOKEN) {
966             request.setUrl(d->m_oauth1Data.TokenEndpoint());
967             authHeader = createOAuthHeader(d->m_oauth1Data.TokenEndpoint(),
968                                            d->m_oauth1Data);
969         }
970         else {
971             Q_ASSERT_X(false, "sendOAuth1PostRequest", "Invalid OAuth1 POST request");
972         }
973         request.setRawHeader(QByteArray("Authorization"), authHeader.toAscii());
974
975         d->m_reply = d->m_manager->post(request, QByteArray());
976
977         connect(d->m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
978                 this, SLOT(slotError(QNetworkReply::NetworkError)));
979         connect(d->m_reply, SIGNAL(sslErrors(QList<QSslError>)),
980                 this, SLOT(slotSslErrors(QList<QSslError>)));
981     }
982
983     const QByteArray OAuth2Plugin::parseReply(const QByteArray &reply,
984                                               const QByteArray &find)
985     {
986         TRACE();
987         QList<QByteArray> list = reply.split('\n');
988         for (int i = 0; i<list.count(); i++) {
989             if (list.at(i).simplified().startsWith(find))
990                 return list.at(i).mid((list.at(i).indexOf(":")+2),
991                                       (list.at(i).size() - 1 - (list.at(i).indexOf(":")+2)));
992         }
993         return QByteArray();
994     }
995
996     const QByteArray OAuth2Plugin::parsePlainTextReply(const QByteArray &reply,
997                                                        const QByteArray &find)
998     {
999         TRACE();
1000         QList<QByteArray> list = reply.split('&');
1001         for (int i = 0; i<list.count(); i++) {
1002             if (list.at(i).startsWith(find)) {
1003                 return list.at(i).mid(list.at(i).indexOf("=") + 1);
1004             }
1005         }
1006         return QByteArray();
1007     }
1008
1009     SIGNON_DECL_AUTH_PLUGIN(OAuth2Plugin)
1010         } //namespace OAuth2PluginNS