Fixed a mysterious login error that occured on some machines
[situare] / src / facebookservice / facebookauthentication.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5        Ville Tiensuu - ville.tiensuu@ixonos.com
6        Kaj Wallin - kaj.wallin@ixonos.com
7        Henri Lampela - henri.lampela@ixonos.com
8
9    Situare is free software; you can redistribute it and/or
10    modify it under the terms of the GNU General Public License
11    version 2 as published by the Free Software Foundation.
12
13    Situare is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with Situare; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
21    USA.
22 */
23
24 #include <QtGui>
25 #include <QtDebug>
26 #include <QDateTime>
27
28 #ifdef Q_WS_MAEMO_5
29 #include <QMaemo5InformationBox>
30 #endif // Q_WS_MAEMO_5
31
32 #include "facebookauthentication.h"
33 #include "facebookcommon.h"
34 #include "parser.h"
35
36 FacebookAuthentication::FacebookAuthentication(QWidget *parent)
37     : QMainWindow(parent),
38     m_email(),
39     m_loginAttempts(0),
40     m_password(),
41     m_refresh(0),
42     m_webView(0)
43 {
44     qDebug() << __PRETTY_FUNCTION__;
45
46     m_mainlayout = new QHBoxLayout;
47
48     connect(this, SIGNAL(loginFailure()),
49             this, SLOT(loginFailed()));
50
51     readCredentials(m_loginCredentials);
52 }
53
54 FacebookAuthentication::~FacebookAuthentication()
55 {
56     qDebug() << __PRETTY_FUNCTION__;
57
58     if(m_webView)
59         delete m_webView;
60
61     delete m_mainlayout;
62 }
63
64 void FacebookAuthentication::loginDialogDone(const QString &email, const QString &password)
65 {
66     qDebug() << __PRETTY_FUNCTION__;
67
68     m_email = email;
69     m_password = password;
70 }
71
72 void FacebookAuthentication::start()
73 {
74     qDebug() << __PRETTY_FUNCTION__;
75
76     if (!verifyCredentials(m_loginCredentials)) {
77
78         m_webView = new QWebView;
79         m_loginDialog = new LoginDialog(this);
80
81         connect(m_webView, SIGNAL(urlChanged(const QUrl &)),
82                 this, SLOT(updateCredentials(const QUrl &)));
83         connect(m_webView, SIGNAL(loadFinished(bool)),
84                 this, SLOT(loadDone(bool)));
85
86         connect(m_loginDialog, SIGNAL(loginDialogDone(QString,QString)),
87                 this, SLOT(loginDialogDone(QString,QString)));
88
89         if(m_loginDialog->exec() != QDialog::Accepted) {
90             // if login dialog was canceled we need to stop processing webview
91             // stop and disconnect m_webView;
92             m_webView->stop();
93             disconnect(m_webView, SIGNAL(loadFinished(bool)),
94                        this, SLOT(loadDone(bool)));
95             disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
96                        this, SLOT(updateCredentials(const QUrl &)));
97
98             emit quitSituare();
99         }
100         QStringList list;
101         list.append(FACEBOOK_LOGINBASE);
102         list.append(SITUARE_PUBLIC_FACEBOOKAPI_KEY);
103         list.append(INTERVAL1);
104         list.append(SITUARE_LOGIN_SUCCESS);
105         list.append(INTERVAL2);
106         list.append(SITUARE_LOGIN_FAILURE);
107         list.append(FACEBOOK_LOGIN_ENDING);
108
109         m_webView->load(formLoginPageUrl(list));
110         toggleProgressIndicator(true);
111         m_refresh = true;
112         setCentralWidget(m_webView);
113         m_webView->hide();
114         this->show();
115     }
116     else
117         emit credentialsReady(m_loginCredentials);
118 }
119
120 void FacebookAuthentication::loadDone(bool done)
121 {
122     qDebug() << __PRETTY_FUNCTION__;
123
124     // for the first time the login page is opened, we need to refresh it to get cookies working
125     if(m_refresh) {
126         QStringList list;
127         list.append(FACEBOOK_LOGINBASE);
128         list.append(SITUARE_PUBLIC_FACEBOOKAPI_KEY);
129         list.append(INTERVAL1);
130         list.append(SITUARE_LOGIN_SUCCESS);
131         list.append(INTERVAL2);
132         list.append(SITUARE_LOGIN_FAILURE);
133         list.append(FACEBOOK_LOGIN_ENDING);
134
135         m_webView->load(formLoginPageUrl(list));
136         m_refresh = false;
137     }
138
139     if (done)
140     {
141         QWebFrame* frame = m_webView->page()->currentFrame();
142         if (frame!=NULL)
143         {
144             // set email box
145             QWebElementCollection emailCollection = frame->findAllElements("input[name=email]");
146
147             foreach (QWebElement element, emailCollection) {
148                 element.setAttribute("value", m_email.toAscii());
149             }
150             // set password box
151             QWebElementCollection passwordCollection = frame->findAllElements("input[name=pass]");
152             foreach (QWebElement element, passwordCollection) {
153                 element.setAttribute("value", m_password.toAscii());
154             }
155             // find connect button
156             QWebElementCollection buttonCollection = frame->findAllElements("input[name=login]");
157             foreach (QWebElement element, buttonCollection)
158             {
159                 QPoint pos(element.geometry().center());
160
161                 // send a mouse click event to the web page
162                 QMouseEvent event0(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton,
163                                    Qt::NoModifier);
164                 QApplication::sendEvent(m_webView->page(), &event0);
165                 QMouseEvent event1(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton,
166                                    Qt::NoModifier);
167                 QApplication::sendEvent(m_webView->page(), &event1);
168             }
169         }
170     }
171 }
172
173 void FacebookAuthentication::loginFailed()
174 {
175     qDebug() << __PRETTY_FUNCTION__;
176
177     m_email.clear();
178     m_password.clear();
179
180     toggleProgressIndicator(false);
181
182 #ifdef Q_WS_MAEMO_5
183     QMaemo5InformationBox::information(this, tr("Invalid E-mail address or password"),
184                                        QMaemo5InformationBox::NoTimeout);
185
186 #endif // Q_WS_MAEMO_5
187
188     if(m_loginDialog->exec() != QDialog::Accepted) {
189         // if login dialog was canceled we need to stop processing webview        
190         // stop and disconnect m_webView;
191         m_webView->stop();
192         disconnect(m_webView, SIGNAL(loadFinished(bool)),
193                    this, SLOT(loadDone(bool)));
194         disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
195                    this, SLOT(updateCredentials(const QUrl &)));
196
197         emit quitSituare();
198     }
199     else {
200         // re-load login page for webview
201         toggleProgressIndicator(true);
202         QStringList list;
203         list.append(FACEBOOK_LOGINBASE);
204         list.append(SITUARE_PUBLIC_FACEBOOKAPI_KEY);
205         list.append(INTERVAL1);
206         list.append(SITUARE_LOGIN_SUCCESS);
207         list.append(INTERVAL2);
208         list.append(SITUARE_LOGIN_FAILURE);
209         list.append(FACEBOOK_LOGIN_ENDING);
210
211         m_webView->load(formLoginPageUrl(list));
212     }
213 }
214
215 bool FacebookAuthentication::updateCredentials(const QUrl &url)
216 {
217     qDebug() << __PRETTY_FUNCTION__;
218
219     bool found = false;
220
221     if (url.isValid()){
222          qDebug() << "url is valid";
223
224         QString callbackUrl = url.toString();
225         qDebug() << "callbackUrl:  " << callbackUrl.toAscii();
226
227         if (callbackUrl.indexOf(LOGIN_SUCCESS_REPLY) == 0) {
228             qDebug() << "login success";
229
230             disconnect(m_webView, SIGNAL(loadFinished(bool)),
231                        this, SLOT(loadDone(bool)));
232             disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
233                        this, SLOT(updateCredentials(const QUrl &)));
234
235             // let's find out session credentials
236             if(callbackUrl.contains(SESSION_KEY)) {
237
238                 QJson::Parser parser;
239                 bool ok;
240
241                 // split string into string part and json part
242                 QStringList list = url.toString().split("=");
243
244                 for(int i=0;i<list.count();i++) {
245                     // if string starts with json item
246                     if(list.at(i).startsWith("{")) {
247                         QByteArray jsonString = list.at(i).toAscii();
248                         QVariantMap result = parser.parse (jsonString, &ok).toMap();
249                         if (!ok) {
250
251                             qFatal("An error occurred during parsing");
252                             exit (1);
253                         }
254                         qDebug() << "Session Key" << result[SESSION_KEY].toString();
255                         m_loginCredentials.setSessionKey(result[SESSION_KEY].toString());
256
257                         qDebug() << "userID" << result[USER_ID].toString();
258                         m_loginCredentials.setUserID(result[USER_ID].toString());
259
260                         qDebug() << "Expires" << result[EXPIRES].toString();
261                         m_loginCredentials.setExpires(result[EXPIRES].toString());
262
263                         qDebug() << "Session Secret" << result[SESSION_SECRET].toString();
264                         m_loginCredentials.setSessionSecret(result[SESSION_SECRET].toString());
265
266                         qDebug() << "Signature" << result[SIGNATURE].toString();
267                         m_loginCredentials.setSig(result[SIGNATURE].toString());
268                     }
269                 }
270                 found = true;
271             }
272             writeCredentials(m_loginCredentials);
273             emit credentialsReady(m_loginCredentials);
274         }
275         else if ( callbackUrl.indexOf(LOGIN_FAILURE_REPLY) == 0){
276             qWarning() << "login failure" << endl;
277             qDebug() << callbackUrl;
278             ++m_loginAttempts;
279             /* emit loginFailure for every second login attemps, since webview loads login
280                error page (loadingDone() signal is emitted) and we need to avoid that because
281                at this point we don't have new login parameters */
282             if(m_loginAttempts % 2) {
283                 emit loginFailure();
284             }
285         }
286         else if(callbackUrl.indexOf(LOGIN_PAGE) == 0) {
287             qDebug() << "correct loginPage";
288         }
289         else {
290             qDebug() << "totally wrong webPage";
291             // we should not get a wrong page at this point
292             emit loginFailure();
293         }
294     }
295     else {
296         qDebug() << " Loading of page failed invalid URL" << endl;
297         // we should not get a wrong page at this point
298         emit loginFailure();
299         return false;
300     }
301     return found;
302 }
303
304 void FacebookAuthentication::writeCredentials(const FacebookCredentials &credentials)
305 {
306     qDebug() << __PRETTY_FUNCTION__;
307     QSettings settings(DIRECTORY_NAME, FILE_NAME);
308
309     settings.setValue(SESSION_KEY, credentials.sessionKey());
310     settings.setValue(USER_ID, credentials.userID());
311     settings.setValue(EXPIRES, credentials.expires());
312     settings.setValue(SESSION_SECRET, credentials.sessionSecret());
313     settings.setValue(SIGNATURE, credentials.sig());
314 }
315
316 void FacebookAuthentication::readCredentials(FacebookCredentials &credentialsFromFile)
317 {
318     qDebug() << __PRETTY_FUNCTION__;
319
320     QSettings settings(DIRECTORY_NAME, FILE_NAME);
321
322     credentialsFromFile.setSessionKey(settings.value(SESSION_KEY, ERROR).toString());
323     credentialsFromFile.setUserID(settings.value(USER_ID, ERROR).toString());
324     credentialsFromFile.setExpires(settings.value(EXPIRES, ERROR).toString());
325     credentialsFromFile.setSessionSecret(settings.value(SESSION_SECRET, ERROR).toString());
326     credentialsFromFile.setSig(settings.value(SIGNATURE, ERROR).toString());
327 }
328
329  FacebookCredentials FacebookAuthentication::loginCredentials() const
330  {
331      qDebug() << __PRETTY_FUNCTION__;
332      return m_loginCredentials;
333  }
334
335  bool FacebookAuthentication::verifyCredentials(const FacebookCredentials &credentials) const
336  {
337      qDebug() << __PRETTY_FUNCTION__;
338
339      // if expires value is 0, then credentials are valid forever
340      if(credentials.expires() == "0") {
341          return true;
342      }
343      else {
344          const QString dateTimeFormat = "dd.MM.yyyy  hh:mm:ss";
345          QString expires = credentials.expires();
346          QDateTime expireTime;
347          expireTime.setTime_t(expires.toInt());
348          QString expiresString = expireTime.toString(dateTimeFormat);
349          qDebug() << expiresString.toAscii();
350
351          QDateTime currentTime;
352          currentTime = QDateTime::currentDateTime();
353          QString currentTimeString = currentTime.toString(dateTimeFormat);
354          qDebug() << currentTimeString.toAscii();
355
356          return currentTime < expireTime;
357      }
358  }
359
360  QUrl FacebookAuthentication::formLoginPageUrl(const QStringList &urlParts) const
361  {
362     qDebug() << __PRETTY_FUNCTION__;
363
364     return QUrl(urlParts.join(EMPTY));
365  }
366
367  void FacebookAuthentication::toggleProgressIndicator(bool value)
368  {
369      qDebug() << __PRETTY_FUNCTION__;
370  #ifdef Q_WS_MAEMO_5
371      setAttribute(Qt::WA_Maemo5ShowProgressIndicator, value);
372  #else
373      Q_UNUSED(value);
374  #endif // Q_WS_MAEMO_5
375  }