Implemented custom login and created functional tests for it
[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
35 FacebookAuthentication::FacebookAuthentication(QWidget *parent)
36     : QMainWindow(parent)
37 {
38     qDebug() << __PRETTY_FUNCTION__;
39
40     m_refresh = false;
41     m_email.clear();
42     m_password.clear();
43     m_loginAttempts = 0;
44
45     m_loginDialog = new LoginDialog(this);
46
47     m_webView = new QWebView;
48     m_mainlayout = new QHBoxLayout;
49
50     m_facebookLoginPage = formLoginPage(FACEBOOK_LOGINBASE, SITUARE_PUBLIC_FACEBOOKAPI_KEY,
51                                         INTERVAL1, SITUARE_LOGIN_SUCCESS, INTERVAL2,
52                                         SITUARE_LOGIN_FAILURE, FACEBOOK_LOGIN_ENDING);
53
54     connect(m_webView, SIGNAL(urlChanged(const QUrl &)),
55             this, SLOT(updateCredentials(const QUrl &)));
56     connect(m_webView, SIGNAL(loadFinished(bool)),
57             this, SLOT(loadDone(bool)));
58
59     connect(m_loginDialog, SIGNAL(loginDialogDone(QString,QString)),
60             this, SLOT(loginDialogDone(QString,QString)));
61
62     connect(this, SIGNAL(loginFailure()),
63             this, SLOT(loginFailed()));
64
65     readCredentials(m_loginCredentials);
66 }
67
68 FacebookAuthentication::~FacebookAuthentication()
69 {
70     qDebug() << __PRETTY_FUNCTION__;
71
72     if(m_webView) {
73         delete m_webView;
74         m_webView = 0;
75     }
76     if(m_mainlayout) {
77         delete m_mainlayout;
78         m_mainlayout = 0;
79     }
80     if(m_loginDialog) {
81         delete m_loginDialog;
82         m_loginDialog = 0;
83     }
84 }
85
86 void FacebookAuthentication::loginDialogDone(const QString &email, const QString &password)
87 {
88     qDebug() << __PRETTY_FUNCTION__;
89
90     m_email.append(email);
91     m_password.append(password);
92 }
93
94 void FacebookAuthentication::start()
95 {
96     qDebug() << __PRETTY_FUNCTION__;    
97     if (!verifyCredentials(m_loginCredentials)){
98         if(m_loginDialog->exec() != QDialog::Accepted) {
99             // if login dialog was canceled we need to stop processing webview            
100             // stop and disconnect m_webView;
101             m_webView->stop();
102             disconnect(m_webView, SIGNAL(loadFinished(bool)),
103                        this, SLOT(loadDone(bool)));
104             disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
105                        this, SLOT(updateCredentials(const QUrl &)));
106
107             emit quitSituare();
108         }
109         m_webView->setZoomFactor(FACEBOOK_LOGINPAGE_FONT_SIZE);
110         m_webView->load(m_facebookLoginPage);
111         this->toggleProgressIndicator(true);
112         m_refresh = true;
113         setCentralWidget(m_webView);
114         m_webView->hide();
115         this->show();
116     }
117     else
118         emit credentialsReady(m_loginCredentials);
119 }
120
121 void FacebookAuthentication::loadDone(bool done)
122 {
123     qDebug() << __PRETTY_FUNCTION__;
124
125     // for the first time the login page is opened, we need to refresh it to get cookies working
126     if(m_refresh) {
127         m_webView->load(m_facebookLoginPage);
128         m_refresh = false;
129     }
130
131     if (done)
132     {
133         this->toggleProgressIndicator(false);
134         QWebFrame* frame = m_webView->page()->currentFrame();
135         if (frame!=NULL)
136         {
137             // set email box
138             QWebElementCollection emailCollection = frame->findAllElements("input[name=email]");
139
140             foreach (QWebElement element, emailCollection) {
141                 element.setAttribute("value", m_email.toAscii());
142             }
143             // set password box
144             QWebElementCollection passwordCollection = frame->findAllElements("input[name=pass]");
145             foreach (QWebElement element, passwordCollection) {
146                 element.setAttribute("value", m_password.toAscii());
147             }
148             // find connect button
149             QWebElementCollection buttonCollection = frame->findAllElements("input[name=login]");
150             foreach (QWebElement element, buttonCollection)
151             {
152                 QPoint pos(element.geometry().center());
153
154                 // send a mouse click event to the web page
155                 QMouseEvent event0(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
156                 QApplication::sendEvent(m_webView->page(), &event0);
157                 QMouseEvent event1(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
158                 QApplication::sendEvent(m_webView->page(), &event1);
159             }
160         }
161     }
162 }
163
164 void FacebookAuthentication::loginFailed()
165 {
166     qDebug() << __PRETTY_FUNCTION__;
167
168     m_email.clear();
169     m_password.clear();
170
171 #ifdef Q_WS_MAEMO_5
172     QMaemo5InformationBox::information(this, tr("Invalid E-mail address or password"),
173                                        QMaemo5InformationBox::NoTimeout);
174
175 #endif // Q_WS_MAEMO_5
176
177     if(m_loginDialog->exec() != QDialog::Accepted) {
178         // if login dialog was canceled we need to stop processing webview        
179         // stop and disconnect m_webView;
180         m_webView->stop();
181         disconnect(m_webView, SIGNAL(loadFinished(bool)),
182                    this, SLOT(loadDone(bool)));
183         disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
184                    this, SLOT(updateCredentials(const QUrl &)));
185
186         emit quitSituare();
187     }
188     else {
189         // re-load login page for webview
190         this->toggleProgressIndicator(true);
191         m_webView->setZoomFactor(FACEBOOK_LOGINPAGE_FONT_SIZE);
192         m_webView->load(m_facebookLoginPage);
193     }
194 }
195
196 bool FacebookAuthentication::updateCredentials(const QUrl &url)
197 {    
198     qDebug() << __PRETTY_FUNCTION__;
199
200     bool foundSessionKey = FALSE;
201     bool foundUserID = FALSE;
202     bool foundExpires = FALSE;
203     bool foundSessionSecret = FALSE;
204     bool foundSig = FALSE;
205
206     if (url.isValid()){
207          qDebug() << "url is valid" << endl;
208
209         QString callbackUrl = url.toString();
210         QString urlEdit(callbackUrl);
211         qDebug() << "callbackUrl:  " << endl << callbackUrl.toAscii() << endl;
212
213         if ( callbackUrl.indexOf(LOGIN_SUCCESS_REPLY) == 0 ){
214             qDebug() << "login success" << endl;
215
216             disconnect(m_webView, SIGNAL(loadFinished(bool)),
217                        this, SLOT(loadDone(bool)));
218             disconnect(m_webView, SIGNAL(urlChanged(const QUrl &)),
219                        this, SLOT(updateCredentials(const QUrl &)));
220
221             // let's find out session key            
222             int indexOfCredential = callbackUrl.indexOf(SESSION_KEY);
223
224             if (indexOfCredential != -1){
225                 foundSessionKey = TRUE;
226
227                 indexOfCredential += 14; //lenght of SESSION_KEY
228                 urlEdit.remove(0,indexOfCredential);
229                 indexOfCredential = urlEdit.indexOf(USER_ID);
230                 urlEdit.remove(indexOfCredential, urlEdit.length());
231                 urlEdit.remove("\",\"");
232
233                 qDebug() << "Session Key" << endl << urlEdit.toAscii() << endl;
234                 m_loginCredentials.setSessionKey(urlEdit);
235             }
236
237             // let's find out uid            
238             urlEdit = callbackUrl;
239             indexOfCredential = callbackUrl.indexOf(USER_ID);
240
241             if (indexOfCredential != -1){
242                 foundUserID = TRUE;
243
244                 indexOfCredential += 5; //length of USER_ID:
245                 urlEdit.remove(0,indexOfCredential);
246                 indexOfCredential = urlEdit.indexOf(EXPIRES);
247                 urlEdit.remove(indexOfCredential, urlEdit.length());
248                 urlEdit.remove(",\"");
249
250                 qDebug() << "userID" << endl << urlEdit.toAscii() << endl;
251                 m_loginCredentials.setUserID(urlEdit);
252             }
253
254             // let's find out expires           
255             urlEdit = callbackUrl;
256             indexOfCredential = callbackUrl.indexOf(EXPIRES);
257
258             if (indexOfCredential != -1){
259                 foundExpires = TRUE;
260
261                 indexOfCredential += 9; //length of EXPIRES
262                 urlEdit.remove(0,indexOfCredential);
263                 indexOfCredential = urlEdit.indexOf(SESSION_SECRET);
264                 urlEdit.remove(indexOfCredential, urlEdit.length());
265                 urlEdit.remove(",\"");
266
267                 qDebug() << "Expires" << endl << urlEdit.toAscii() << endl;
268                 m_loginCredentials.setExpires(urlEdit);
269             }
270
271             // let's find out sessionsecret            
272             urlEdit = callbackUrl;
273             indexOfCredential = callbackUrl.indexOf(SESSION_SECRET);
274
275             if (indexOfCredential != -1){
276                 foundSessionSecret = TRUE;
277
278                 indexOfCredential += 9; //" length of SESSION_SECRET
279                 urlEdit.remove(0,indexOfCredential);
280                 indexOfCredential = urlEdit.indexOf(SIGNATURE);
281                 urlEdit.remove(indexOfCredential, urlEdit.length());
282                 urlEdit.remove("\",\"");
283
284                 qDebug() << "Session Secret" << endl << urlEdit.toAscii() << endl;
285                 m_loginCredentials.setSessionSecret(urlEdit);
286             }
287
288             // let's find out sig            
289             urlEdit = callbackUrl;
290             indexOfCredential = callbackUrl.indexOf(SIGNATURE);
291
292             if (indexOfCredential != -1){
293                 foundSig = TRUE;
294
295                 indexOfCredential += 6; //" length of SIGNATURE
296                 urlEdit.remove(0,indexOfCredential);
297                 urlEdit.remove("\"}");
298
299                 qDebug() << "Signature" << endl << urlEdit.toAscii() << endl;
300                 m_loginCredentials.setSig(urlEdit);
301             }
302
303             m_webView->hide();
304             writeCredentials(m_loginCredentials);
305             emit credentialsReady(m_loginCredentials);
306         }
307
308         else if ( callbackUrl.indexOf(LOGIN_FAILURE_REPLY) == 0){
309             qWarning() << "login failure" << endl;
310             qDebug() << callbackUrl;
311             ++m_loginAttempts;
312             /* emit loginFailure for every second login attemps, since webview loads login
313                error page (loadingDone() signal is emitted) and we need to avoid that because
314                at this point we don't have new login parameters */
315             if(m_loginAttempts % 2) {
316                 emit loginFailure();
317             }
318         }
319
320         else if ( callbackUrl.indexOf(LOGIN_PAGE) == 0){
321             qDebug() << "correct loginPage";
322         }
323
324         else {
325             qDebug() << "totally wrong webPage";
326             // we should not get a wrong page at this point
327             emit loginFailure();
328         }
329     }
330
331     else {
332         qDebug() << " Loading of page failed invalid URL" << endl;
333         // we should not get a wrong page at this point
334         emit loginFailure();
335         return FALSE;
336     }
337
338     return (foundSessionKey && foundUserID && foundExpires && foundSessionSecret && foundSig);
339 }
340
341 void FacebookAuthentication::writeCredentials(const FacebookCredentials &credentials)
342 {
343     qDebug() << __PRETTY_FUNCTION__;
344     QSettings settings(DIRECTORY_NAME, FILE_NAME);
345
346     settings.setValue("Session Key", credentials.sessionKey());
347     settings.setValue("User ID", credentials.userID());
348     settings.setValue("Expires", credentials.expires());
349     settings.setValue("Session Secret", credentials.sessionSecret());
350     settings.setValue("Sig", credentials.sig());
351 }
352
353 void FacebookAuthentication::readCredentials(FacebookCredentials &credentialsFromFile)
354 {
355     qDebug() << __PRETTY_FUNCTION__;
356
357     QSettings settings(DIRECTORY_NAME, FILE_NAME);
358
359     credentialsFromFile.setSessionKey(settings.value("Session Key", "Error").toString());
360     credentialsFromFile.setUserID(settings.value("User ID", "Error").toString());
361     credentialsFromFile.setExpires(settings.value("Expires", "Error").toString());
362     credentialsFromFile.setSessionSecret(settings.value("Session Secret", "Error").toString());
363     credentialsFromFile.setSig(settings.value("Sig", "Error").toString());
364 }
365
366  FacebookCredentials FacebookAuthentication::loginCredentials() const
367  {
368      qDebug() << __PRETTY_FUNCTION__;
369      return m_loginCredentials;
370  }
371
372  bool FacebookAuthentication::verifyCredentials(const FacebookCredentials &credentials) const
373  {
374      qDebug() << __PRETTY_FUNCTION__;
375
376      // if expires value is 0, then credentials are valid forever
377      if(credentials.expires() == "0") {
378          return true;
379      }
380      else {
381          QString expires = credentials.expires();
382          QDateTime expireTime;
383          expireTime.setTime_t(expires.toInt());
384          QString expiresString = expireTime.toString("dd.MM.yyyy  hh:mm:ss");
385          qDebug() << expiresString.toAscii();
386
387          QDateTime currentTime;
388          currentTime = QDateTime::currentDateTime();
389          QString currentTimeString = currentTime.toString("dd.MM.yyyy  hh:mm:ss");
390          qDebug() << currentTimeString.toAscii();
391
392          return currentTime < expireTime;
393      }
394  }
395
396  QString FacebookAuthentication::formLoginPage(const QString & part1, const QString & part2,
397                                  const QString & part3, const QString & part4,
398                                  const QString & part5, const QString & part6,
399                                  const QString & part7) const
400  {
401      QString loginPage;
402      loginPage.append(part1);
403      loginPage.append(part2);
404      loginPage.append(part3);
405      loginPage.append(part4);
406      loginPage.append(part5);
407      loginPage.append(part5);
408      loginPage.append(part6);
409      loginPage.append(part7);
410
411      return loginPage;
412  }
413
414  void FacebookAuthentication::toggleProgressIndicator(bool value)
415  {
416      qDebug() << __PRETTY_FUNCTION__;
417  #ifdef Q_WS_MAEMO_5
418      setAttribute(Qt::WA_Maemo5ShowProgressIndicator, value);
419  #else
420      Q_UNUSED(value);
421  #endif // Q_WS_MAEMO_5
422  }