[wellsfargo] Update to new login security checks. Closes #1999
This commit is contained in:
parent
45f20ff158
commit
337b827802
3 changed files with 132 additions and 19 deletions
|
|
@ -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);
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")]'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue