From 337b8278024a1644a9336d71b28e678d7a0ecf34 Mon Sep 17 00:00:00 2001 From: Oleg Plakhotniuk Date: Thu, 25 Jun 2015 04:42:57 -0500 Subject: [PATCH] [wellsfargo] Update to new login security checks. Closes #1999 --- modules/wellsfargo/browser.py | 121 ++++++++++++++++++++++++++++++++-- modules/wellsfargo/module.py | 22 +++++-- modules/wellsfargo/pages.py | 8 --- 3 files changed, 132 insertions(+), 19 deletions(-) diff --git a/modules/wellsfargo/browser.py b/modules/wellsfargo/browser.py index 761ab17f..1da21248 100644 --- a/modules/wellsfargo/browser.py +++ b/modules/wellsfargo/browser.py @@ -22,8 +22,13 @@ from weboob.capabilities.bank import AccountNotFound from weboob.browser import LoginBrowser, URL, need_login from weboob.exceptions import BrowserIncorrectPassword import ssl +import json +import os +from tempfile import mkstemp +from subprocess import check_output, STDOUT +from urllib import unquote -from .pages import LoginPage, LoginProceedPage, LoginRedirectPage, \ +from .pages import LoginProceedPage, LoginRedirectPage, \ SummaryPage, ActivityCashPage, ActivityCardPage, \ StatementsPage, StatementPage, LoggedInPage @@ -33,7 +38,8 @@ __all__ = ['WellsFargo'] class WellsFargo(LoginBrowser): BASEURL = 'https://online.wellsfargo.com' - login = URL('/$', LoginPage) + TIMEOUT = 30 + MAX_RETRIES = 10 login_proceed = URL('/das/cgi-bin/session.cgi\?screenid=SIGNON.*$', '/login\?ERROR_CODE=.*LOB=CONS&$', LoginProceedPage) @@ -53,12 +59,54 @@ class WellsFargo(LoginBrowser): StatementPage) unknown = URL('/.*$', LoggedInPage) # E.g. random advertisement pages. + def __init__(self, question1, answer1, question2, answer2, + question3, answer3, *args, **kwargs): + super(WellsFargo, self).__init__(*args, **kwargs) + self.question1 = question1 + self.answer1 = answer1 + self.question2 = question2 + self.answer2 = answer2 + self.question3 = question3 + self.answer3 = answer3 + def do_login(self): - self.session.cookies.clear() - self.login.go() - self.page.login(self.username, self.password) - if not self.page.logged: - raise BrowserIncorrectPassword() + ''' + There's a bunch of dynamically generated obfuscated JavaScript, + which uses DOM. For now the easiest option seems to be to run it in + PhantomJs. + ''' + for i in xrange(self.MAX_RETRIES): + scrf, scrn = mkstemp('.js') + cookf, cookn = mkstemp('.json') + os.write(scrf, LOGIN_JS % { + 'timeout': self.TIMEOUT, + 'username': self.username, + 'password': self.password, + 'output': cookn, + 'question1': self.question1, + 'answer1': self.answer1, + 'question2': self.question2, + 'answer2': self.answer2, + 'question3': self.question3, + 'answer3': self.answer3}) + os.close(scrf) + os.close(cookf) + check_output(["phantomjs", scrn], stderr=STDOUT) + with open(cookn) as cookf: + cookies = json.loads(cookf.read()) + os.remove(scrn) + os.remove(cookn) + self.session.cookies.clear() + for c in cookies: + for k in ['expiry', 'expires', 'httponly']: + c.pop(k, None) + c['value'] = unquote(c['value']) + self.session.cookies.set(**c) + self.summary.go() + if self.page.logged: + break + else: + raise BrowserIncorrectPassword def location(self, *args, **kwargs): """ @@ -160,3 +208,62 @@ class WellsFargo(LoginBrowser): self.to_statement(stmt) for trans in self.page.iter_transactions(): yield trans + +LOGIN_JS = u'''\ +var TIMEOUT = %(timeout)s*1000; // milliseconds +var page = require('webpage').create(); + +page.open('https://online.wellsfargo.com/'); + +var waitForForm = function() { + var hasForm = page.evaluate(function(){ + return !!document.getElementById('Signon') + }); + if (hasForm) { + page.evaluate(function(){ + document.getElementById('username').value = '%(username)s'; + document.getElementById('password').value = '%(password)s'; + document.getElementById('Signon').submit(); + }); + } else { + setTimeout(waitForForm, 1000); + } +} + +var waitForQuestions = function() { + var isQuestion = page.content.indexOf('Confirm Your Identity') != -1; + if (isQuestion) { + var questions = { + "%(question1)s": "%(answer1)s", + "%(question2)s": "%(answer2)s", + "%(question3)s": "%(answer3)s" + }; + for (var question in questions) { + if (page.content.indexOf(question)) { + page.evaluate(function(answer){ + document.getElementById('answer').value = answer; + document.getElementById('command').submit.click(); + }, questions[question]); + } + } + } + setTimeout(waitForQuestions, 2000); +} + +var waitForLogin = function() { + var isSplash = page.content.indexOf('Splash Page') != -1; + var hasSignOff = page.content.indexOf('Sign Off') != -1; + if (isSplash || hasSignOff) { + var cookies = JSON.stringify(phantom.cookies); + require('fs').write('%(output)s', cookies, 'w'); + phantom.exit(); + } else { + setTimeout(waitForLogin, 2000); + } +} + +waitForForm(); +waitForQuestions(); +waitForLogin(); +setTimeout(function(){phantom.exit(-1);}, TIMEOUT); +''' diff --git a/modules/wellsfargo/module.py b/modules/wellsfargo/module.py index 5886aea9..d6e3bbca 100644 --- a/modules/wellsfargo/module.py +++ b/modules/wellsfargo/module.py @@ -35,13 +35,27 @@ class WellsFargoModule(Module, CapBank): VERSION = '1.1' LICENSE = 'AGPLv3+' DESCRIPTION = u'Wells Fargo' - CONFIG = BackendConfig(ValueBackendPassword('login', label='Username', masked=False), - ValueBackendPassword('password', label='Password')) + CONFIG = BackendConfig( + ValueBackendPassword('login', label='Username', masked=False), + ValueBackendPassword('password', label='Password'), + ValueBackendPassword('question1', label='Question 1', masked=False), + ValueBackendPassword('answer1', label='Answer 1', masked=False), + ValueBackendPassword('question2', label='Question 2', masked=False), + ValueBackendPassword('answer2', label='Answer 2', masked=False), + ValueBackendPassword('question3', label='Question 3', masked=False), + ValueBackendPassword('answer3', label='Answer 3', masked=False)) BROWSER = WellsFargo def create_default_browser(self): - return self.create_browser(self.config['login'].get(), - self.config['password'].get()) + return self.create_browser( + username = self.config['login'].get(), + password = self.config['password'].get(), + question1 = self.config['question1'].get(), + answer1 = self.config['answer1'].get(), + question2 = self.config['question2'].get(), + answer2 = self.config['answer2'].get(), + question3 = self.config['question3'].get(), + answer3 = self.config['answer3'].get()) def iter_accounts(self): return self.browser.iter_accounts() diff --git a/modules/wellsfargo/pages.py b/modules/wellsfargo/pages.py index 7c23af16..42dbf9e7 100644 --- a/modules/wellsfargo/pages.py +++ b/modules/wellsfargo/pages.py @@ -31,14 +31,6 @@ import datetime import Cookie -class LoginPage(HTMLPage): - def login(self, login, password): - form = self.get_form(xpath='//form[@name="Signon"]') - form['userid'] = login - form['password'] = password - form.submit() - - class LoginProceedPage(LoggedPage, HTMLPage): is_here = '//script[contains(text(),"setAndCheckCookie")]'