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