Add two factor authentication for boursorama module
This commit is contained in:
parent
67a6310519
commit
ee4f3793a1
4 changed files with 196 additions and 9 deletions
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2012 Gabriel Serme
|
||||
# Copyright(C) 2011 Gabriel Kerneis
|
||||
# Copyright(C) 2010-2011 Jocelyn Jaubert
|
||||
#
|
||||
|
|
@ -24,7 +25,7 @@ from __future__ import with_statement
|
|||
|
||||
from weboob.capabilities.bank import ICapBank, AccountNotFound
|
||||
from weboob.tools.backend import BaseBackend, BackendConfig
|
||||
from weboob.tools.value import ValueBackendPassword
|
||||
from weboob.tools.value import ValueBackendPassword, ValueBool, Value
|
||||
|
||||
from .browser import Boursorama
|
||||
|
||||
|
|
@ -40,12 +41,17 @@ class BoursoramaBackend(BaseBackend, ICapBank):
|
|||
LICENSE = 'AGPLv3+'
|
||||
DESCRIPTION = u'Boursorama French bank website'
|
||||
CONFIG = BackendConfig(ValueBackendPassword('login', label='Account ID', masked=False),
|
||||
ValueBackendPassword('password', label='Password'))
|
||||
ValueBackendPassword('password', label='Password'),
|
||||
ValueBool('enable_twofactors', label='Send validation sms', default=False),
|
||||
Value('device', label='Device name', regexp='\w*'),)
|
||||
BROWSER = Boursorama
|
||||
|
||||
def create_default_browser(self):
|
||||
return self.create_browser(self.config['login'].get(),
|
||||
self.config['password'].get())
|
||||
return self.create_browser(
|
||||
self.config["device"].get()
|
||||
, self.config["enable_twofactors"].get()
|
||||
, self.config['login'].get()
|
||||
, self.config['password'].get())
|
||||
|
||||
def iter_accounts(self):
|
||||
for account in self.browser.get_accounts_list():
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2012 Gabriel Serme
|
||||
# Copyright(C) 2011 Gabriel Kerneis
|
||||
# Copyright(C) 2010-2011 Jocelyn Jaubert
|
||||
#
|
||||
|
|
@ -20,25 +21,33 @@
|
|||
|
||||
|
||||
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
|
||||
from .pages import LoginPage, AccountsList, AccountHistory, UpdateInfoPage
|
||||
from .pages import LoginPage, AccountsList, AccountHistory, UpdateInfoPage, AuthenticationPage
|
||||
|
||||
|
||||
__all__ = ['Boursorama']
|
||||
|
||||
|
||||
class BrowserIncorrectAuthenticationCode(BrowserIncorrectPassword):
|
||||
pass
|
||||
|
||||
|
||||
class Boursorama(BaseBrowser):
|
||||
DOMAIN = 'www.boursorama.com'
|
||||
PROTOCOL = 'https'
|
||||
CERTHASH = '74429081f489cb723a82171a94350913d42727053fc86cf5bf5c3d65d39ec449'
|
||||
ENCODING = None # refer to the HTML encoding
|
||||
PAGES = {
|
||||
'.*connexion.phtml.*': LoginPage,
|
||||
'.*/comptes/synthese.phtml': AccountsList,
|
||||
'.*/mouvements.phtml.*': AccountHistory,
|
||||
'.*/connexion/securisation/index.phtml': AuthenticationPage,
|
||||
'.*connexion.phtml.*': LoginPage,
|
||||
'.*/comptes/synthese.phtml': AccountsList,
|
||||
'.*/comptes/banque/detail/mouvements.phtml.*': AccountHistory,
|
||||
'.*/date_anniversaire.phtml.*': UpdateInfoPage,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, device="weboob", enable_twofactors=False
|
||||
, *args, **kwargs):
|
||||
self.device = device
|
||||
self.enable_twofactors = enable_twofactors
|
||||
BaseBrowser.__init__(self, *args, **kwargs)
|
||||
|
||||
def home(self):
|
||||
|
|
@ -47,11 +56,25 @@ class Boursorama(BaseBrowser):
|
|||
def is_logged(self):
|
||||
return not self.is_on_page(LoginPage)
|
||||
|
||||
def handle_authentication(self):
|
||||
if self.is_on_page(AuthenticationPage):
|
||||
if self.enable_twofactors:
|
||||
self.page.authenticate(self.device)
|
||||
else:
|
||||
print \
|
||||
"""Boursorama - activate the two factor authentication in boursorama config."""\
|
||||
""" You will receive SMS code but are limited in request per day (around 15)"""
|
||||
|
||||
def login(self):
|
||||
assert isinstance(self.username, basestring)
|
||||
assert isinstance(self.password, basestring)
|
||||
assert isinstance(self.device, basestring)
|
||||
assert isinstance(self.enable_twofactors, bool)
|
||||
assert self.password.isdigit()
|
||||
|
||||
#for debug, save requested pages to tmp dir
|
||||
#self.SAVE_RESPONSES = True
|
||||
|
||||
if not self.is_on_page(LoginPage):
|
||||
self.location('https://' + self.DOMAIN + '/connexion.phtml')
|
||||
|
||||
|
|
@ -60,6 +83,22 @@ class Boursorama(BaseBrowser):
|
|||
if self.is_on_page(LoginPage):
|
||||
raise BrowserIncorrectPassword()
|
||||
|
||||
#after login, we might be redirected to the two factor
|
||||
#authentication page
|
||||
#print "handle authentication"
|
||||
self.handle_authentication()
|
||||
|
||||
self.location('/comptes/synthese.phtml', no_login=True)
|
||||
|
||||
#if the login was correct but authentication code failed,
|
||||
#we need to verify if bourso redirect us to login page or authentication page
|
||||
if self.is_on_page(LoginPage):
|
||||
#print "not correct after handling authentication"
|
||||
raise BrowserIncorrectAuthenticationCode()
|
||||
|
||||
#print "login over"
|
||||
|
||||
|
||||
def get_accounts_list(self):
|
||||
if not self.is_on_page(AccountsList):
|
||||
self.location('/comptes/synthese.phtml')
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from .account_history import AccountHistory
|
|||
from .accounts_list import AccountsList
|
||||
from .login import LoginPage, UpdateInfoPage
|
||||
|
||||
from .two_authentication import AuthenticationPage
|
||||
|
||||
class AccountPrelevement(AccountsList):
|
||||
pass
|
||||
|
|
@ -31,4 +32,5 @@ __all__ = ['LoginPage',
|
|||
'AccountsList',
|
||||
'AccountHistory',
|
||||
'UpdateInfoPage',
|
||||
'AuthenticationPage',
|
||||
]
|
||||
|
|
|
|||
140
modules/boursorama/pages/two_authentication.py
Normal file
140
modules/boursorama/pages/two_authentication.py
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2012 Gabriel Serme
|
||||
#
|
||||
# This file is part of weboob.
|
||||
#
|
||||
# weboob is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# weboob is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from weboob.tools.browser import BasePage, BrowserIncorrectPassword
|
||||
import urllib2
|
||||
import re
|
||||
|
||||
__all__ = ['AuthenticationPage']
|
||||
|
||||
|
||||
class BrowserAuthenticationCodeMaxLimit(BrowserIncorrectPassword):
|
||||
pass
|
||||
|
||||
def write_debug(string, fi):
|
||||
f = open(fi, "w")
|
||||
f.write(string)
|
||||
|
||||
|
||||
class AuthenticationPage(BasePage):
|
||||
|
||||
|
||||
MAX_LIMIT = "vous avez atteint le nombre maximum "\
|
||||
"d'utilisation de l'authentification forte."
|
||||
|
||||
def on_loaded(self):
|
||||
pass
|
||||
|
||||
def authenticate(self, device):
|
||||
"""This function simulates the registration of a device on
|
||||
boursorama two factor authentification web page.
|
||||
I
|
||||
@param device device name to register
|
||||
@exception BrowserAuthenticationCodeMaxLimit when daily limit is consumed
|
||||
@exception BrowserIncorrectAuthenticationCode when code is not correct
|
||||
"""
|
||||
DOMAIN = self.browser.DOMAIN
|
||||
SECURE_PAGE = "https://www.boursorama.com/comptes/connexion/securisation/index.phtml"
|
||||
REFERER = SECURE_PAGE
|
||||
|
||||
#print "Need to authenticate for device", device
|
||||
#print "Domain information", DOMAIN
|
||||
|
||||
url = "https://%s/ajax/banque/otp.phtml?org=%s&alertType=10100" % (DOMAIN, REFERER)
|
||||
#print url
|
||||
headers = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows "
|
||||
"NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"
|
||||
" GTB7.1 (.NET CLR 3.5.30729)",
|
||||
"Referer": REFERER,
|
||||
}
|
||||
|
||||
headers_ajax = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows "
|
||||
"NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"
|
||||
" GTB7.1 (.NET CLR 3.5.30729)",
|
||||
"Accept": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-Request": "JSON",
|
||||
"X-Brs-Xhr-Request": "true",
|
||||
"X-Brs-Xhr-Schema": "DATA+OUT",
|
||||
"Referer": REFERER,
|
||||
}
|
||||
|
||||
req = urllib2.Request(url, headers=headers_ajax)
|
||||
response = self.browser.open(req)
|
||||
#extrat authentication token from response (in form)
|
||||
info = response.read()
|
||||
|
||||
#write_debug(info, "step1.html")
|
||||
|
||||
regex = re.compile(r"vous avez atteint le nombre maximum d'utilisation de l'authentification forte.")
|
||||
r = regex.search(info)
|
||||
if r:
|
||||
print "Boursorama - Vous avez atteint le nombre maximum d'utilisation de l'authentification forte"
|
||||
raise BrowserAuthenticationCodeMaxLimit()
|
||||
|
||||
#print "Response from initial request,", len(info), response.info()
|
||||
regex = re.compile(r"name=\\\"authentificationforteToken\\\" "
|
||||
r"value=\\\"(?P<value>\w*?)\\\"")
|
||||
r = regex.search(info)
|
||||
token = r.group('value')
|
||||
#print "Extracted token", token
|
||||
#self.print_cookies()
|
||||
|
||||
#step2
|
||||
url = "https://" + DOMAIN + "/ajax/banque/otp.phtml"
|
||||
data = "authentificationforteToken=%s&authentificationforteStep=start&alertType=10100&org=%s&validate=" % (token, REFERER)
|
||||
req = urllib2.Request(url, data, headers_ajax)
|
||||
response = self.browser.open(req)
|
||||
#info = response.read()
|
||||
#print "after asking to send token authentification" \
|
||||
# ,len(info), response.info()
|
||||
#write_debug(info, "step2.html")
|
||||
|
||||
#self.print_cookies()
|
||||
|
||||
pin = raw_input('Enter the "Boursorama Banque" access code:')
|
||||
#print "Pin access code: ''%s''" % (pin)
|
||||
url = "https://" + DOMAIN + "/ajax/banque/otp.phtml"
|
||||
data = "authentificationforteToken=%s&authentificationforteStep=otp&alertType=10100&org=%s&otp=%s&validate=" % (token, REFERER, pin)
|
||||
req = urllib2.Request(url, data, headers_ajax)
|
||||
response = self.browser.open(req)
|
||||
#info = response.read()
|
||||
#print "after pin authentification", len(info), response.info()
|
||||
#write_debug(info, "step3.html")
|
||||
|
||||
#self.print_cookies()
|
||||
|
||||
url = "%s?" % (SECURE_PAGE)
|
||||
data = "org=/&device=%s" % (device)
|
||||
req = urllib2.Request(url, data, headers=headers)
|
||||
response = self.browser.open(req)
|
||||
|
||||
#result = response.read()
|
||||
#print response, "\n", response.info()
|
||||
#write_debug(result, "step4.html")
|
||||
|
||||
#self.print_cookies()
|
||||
|
||||
def print_cookies(self):
|
||||
for c in self.browser._ua_handlers["_cookies"].cookiejar:
|
||||
print "%s : %s" % (c.name, c.value)
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue