[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.browser import LoginBrowser, URL, need_login
|
||||||
from weboob.exceptions import BrowserIncorrectPassword
|
from weboob.exceptions import BrowserIncorrectPassword
|
||||||
import ssl
|
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, \
|
SummaryPage, ActivityCashPage, ActivityCardPage, \
|
||||||
StatementsPage, StatementPage, LoggedInPage
|
StatementsPage, StatementPage, LoggedInPage
|
||||||
|
|
||||||
|
|
@ -33,7 +38,8 @@ __all__ = ['WellsFargo']
|
||||||
|
|
||||||
class WellsFargo(LoginBrowser):
|
class WellsFargo(LoginBrowser):
|
||||||
BASEURL = 'https://online.wellsfargo.com'
|
BASEURL = 'https://online.wellsfargo.com'
|
||||||
login = URL('/$', LoginPage)
|
TIMEOUT = 30
|
||||||
|
MAX_RETRIES = 10
|
||||||
login_proceed = URL('/das/cgi-bin/session.cgi\?screenid=SIGNON.*$',
|
login_proceed = URL('/das/cgi-bin/session.cgi\?screenid=SIGNON.*$',
|
||||||
'/login\?ERROR_CODE=.*LOB=CONS&$',
|
'/login\?ERROR_CODE=.*LOB=CONS&$',
|
||||||
LoginProceedPage)
|
LoginProceedPage)
|
||||||
|
|
@ -53,12 +59,54 @@ class WellsFargo(LoginBrowser):
|
||||||
StatementPage)
|
StatementPage)
|
||||||
unknown = URL('/.*$', LoggedInPage) # E.g. random advertisement pages.
|
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):
|
def do_login(self):
|
||||||
self.session.cookies.clear()
|
'''
|
||||||
self.login.go()
|
There's a bunch of dynamically generated obfuscated JavaScript,
|
||||||
self.page.login(self.username, self.password)
|
which uses DOM. For now the easiest option seems to be to run it in
|
||||||
if not self.page.logged:
|
PhantomJs.
|
||||||
raise BrowserIncorrectPassword()
|
'''
|
||||||
|
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):
|
def location(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -160,3 +208,62 @@ class WellsFargo(LoginBrowser):
|
||||||
self.to_statement(stmt)
|
self.to_statement(stmt)
|
||||||
for trans in self.page.iter_transactions():
|
for trans in self.page.iter_transactions():
|
||||||
yield trans
|
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'
|
VERSION = '1.1'
|
||||||
LICENSE = 'AGPLv3+'
|
LICENSE = 'AGPLv3+'
|
||||||
DESCRIPTION = u'Wells Fargo'
|
DESCRIPTION = u'Wells Fargo'
|
||||||
CONFIG = BackendConfig(ValueBackendPassword('login', label='Username', masked=False),
|
CONFIG = BackendConfig(
|
||||||
ValueBackendPassword('password', label='Password'))
|
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
|
BROWSER = WellsFargo
|
||||||
|
|
||||||
def create_default_browser(self):
|
def create_default_browser(self):
|
||||||
return self.create_browser(self.config['login'].get(),
|
return self.create_browser(
|
||||||
self.config['password'].get())
|
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):
|
def iter_accounts(self):
|
||||||
return self.browser.iter_accounts()
|
return self.browser.iter_accounts()
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,6 @@ import datetime
|
||||||
import Cookie
|
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):
|
class LoginProceedPage(LoggedPage, HTMLPage):
|
||||||
is_here = '//script[contains(text(),"setAndCheckCookie")]'
|
is_here = '//script[contains(text(),"setAndCheckCookie")]'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue