use StateBrowser, s/BrowserToBeContinued/BrowserQuestion/ and coding style fixes

This commit is contained in:
Romain Bignon 2015-03-08 11:02:23 +01:00
commit 2afd27b4a4
5 changed files with 64 additions and 77 deletions

View file

@ -23,7 +23,7 @@
import re import re
from collections import defaultdict from collections import defaultdict
from weboob.deprecated.browser import Browser, BrowserIncorrectPassword from weboob.deprecated.browser import StateBrowser, BrowserIncorrectPassword
from weboob.capabilities.bank import Account from weboob.capabilities.bank import Account
from .pages import (LoginPage, AccountsList, AccountHistory, CardHistory, UpdateInfoPage, from .pages import (LoginPage, AccountsList, AccountHistory, CardHistory, UpdateInfoPage,
@ -37,33 +37,31 @@ class BrowserIncorrectAuthenticationCode(BrowserIncorrectPassword):
pass pass
class Boursorama(Browser): class Boursorama(StateBrowser):
DOMAIN = 'www.boursorama.com' DOMAIN = 'www.boursorama.com'
PROTOCOL = 'https' PROTOCOL = 'https'
CERTHASH = ['6bdf8b6dd177bd417ddcb1cfb818ede153288e44115eb269f2ddd458c8461039', CERTHASH = ['6bdf8b6dd177bd417ddcb1cfb818ede153288e44115eb269f2ddd458c8461039',
'b290ef629c88f0508e9cc6305421c173bd4291175e3ddedbee05ee666b34c20e'] 'b290ef629c88f0508e9cc6305421c173bd4291175e3ddedbee05ee666b34c20e']
ENCODING = None # refer to the HTML encoding ENCODING = None # refer to the HTML encoding
PAGES = { PAGES = {r'.*/connexion/securisation.*': AuthenticationPage,
'.*/connexion/securisation.*': AuthenticationPage, r'.*connexion.phtml.*': LoginPage,
'.*connexion.phtml.*': LoginPage, r'.*/comptes/synthese.phtml': AccountsList,
'.*/comptes/synthese.phtml': AccountsList, r'.*/comptes/banque/detail/mouvements.phtml.*': AccountHistory,
'.*/comptes/banque/detail/mouvements.phtml.*': AccountHistory, r'.*/comptes/banque/cartes/mouvements.phtml.*': CardHistory,
'.*/comptes/banque/cartes/mouvements.phtml.*': CardHistory, r'.*/comptes/epargne/mouvements.phtml.*': AccountHistory,
'.*/comptes/epargne/mouvements.phtml.*': AccountHistory, r'.*/date_anniversaire.phtml.*': UpdateInfoPage,
'.*/date_anniversaire.phtml.*': UpdateInfoPage, r'.*/detail.phtml.*': AccountInvestment,
'.*/detail.phtml.*': AccountInvestment, r'.*/opcvm.phtml.*': InvestmentDetail
'.*/opcvm.phtml.*': InvestmentDetail
} }
__states__ = [] __states__ = ('auth_token',)
def __init__(self, config=None, *args, **kwargs): def __init__(self, config=None, *args, **kwargs):
self.config = config self.config = config
self.auth_token = None self.auth_token = None
kwargs['get_home'] = False
kwargs['username'] = self.config['login'].get() kwargs['username'] = self.config['login'].get()
kwargs['password'] = self.config['password'].get() kwargs['password'] = self.config['password'].get()
Browser.__init__(self, *args, **kwargs) StateBrowser.__init__(self, *args, **kwargs)
def home(self): def home(self):
if not self.is_logged(): if not self.is_logged():
@ -77,10 +75,7 @@ class Boursorama(Browser):
def handle_authentication(self): def handle_authentication(self):
if self.is_on_page(AuthenticationPage): if self.is_on_page(AuthenticationPage):
if self.config['enable_twofactors'].get(): if self.config['enable_twofactors'].get():
if not self.config['pin_code'].get() or not self.auth_token: self.page.send_sms()
self.page.send_sms()
else:
self.page.authenticate()
else: else:
raise BrowserIncorrectAuthenticationCode( raise BrowserIncorrectAuthenticationCode(
"""Boursorama - activate the two factor authentication in boursorama config.""" """Boursorama - activate the two factor authentication in boursorama config."""
@ -92,8 +87,8 @@ class Boursorama(Browser):
assert isinstance(self.config['enable_twofactors'].get(), bool) assert isinstance(self.config['enable_twofactors'].get(), bool)
assert self.password.isdigit() assert self.password.isdigit()
if self.is_on_page(AuthenticationPage): if self.auth_token and self.config['pin_code'].get():
self.handle_authentication() AuthenticationPage.authenticate(self)
else: else:
if not self.is_on_page(LoginPage): if not self.is_on_page(LoginPage):
self.location('https://' + self.DOMAIN + '/connexion.phtml', no_login=True) self.location('https://' + self.DOMAIN + '/connexion.phtml', no_login=True)
@ -112,7 +107,7 @@ class Boursorama(Browser):
#if the login was correct but authentication code failed, #if the login was correct but authentication code failed,
#we need to verify if bourso redirect us to login page or authentication page #we need to verify if bourso redirect us to login page or authentication page
if self.is_on_page(LoginPage): if self.is_on_page(LoginPage):
raise BrowserIncorrectAuthenticationCode() raise BrowserIncorrectAuthenticationCode('Invalid PIN code')
def get_accounts_list(self): def get_accounts_list(self):
if self.is_on_page(AuthenticationPage): if self.is_on_page(AuthenticationPage):

View file

@ -49,8 +49,7 @@ class BoursoramaModule(Module, CapBank):
return self.create_browser(self.config) return self.create_browser(self.config)
def iter_accounts(self): def iter_accounts(self):
for account in self.browser.get_accounts_list(): return self.browser.get_accounts_list()
yield account
def get_account(self, _id): def get_account(self, _id):
with self.browser: with self.browser:
@ -61,17 +60,7 @@ class BoursoramaModule(Module, CapBank):
raise AccountNotFound() raise AccountNotFound()
def iter_history(self, account): def iter_history(self, account):
with self.browser: return self.browser.get_history(account)
for history in self.browser.get_history(account):
yield history
def iter_investment(self, account): def iter_investment(self, account):
with self.browser: return self.browser.get_investment(account)
for investment in self.browser.get_investment(account):
yield investment
# TODO
#def iter_coming(self, account):
# with self.browser:
# for coming in self.browser.get_coming_operations(account):
# yield coming

View file

@ -20,8 +20,10 @@
import re import re
import urllib2 import urllib2
from weboob.exceptions import BrowserToBeContinued from weboob.exceptions import BrowserQuestion
from weboob.deprecated.browser import Page, BrowserIncorrectPassword from weboob.deprecated.browser import Page, BrowserIncorrectPassword
from weboob.tools.value import Value
class BrowserAuthenticationCodeMaxLimit(BrowserIncorrectPassword): class BrowserAuthenticationCodeMaxLimit(BrowserIncorrectPassword):
pass pass
@ -34,36 +36,38 @@ class AuthenticationPage(Page):
REFERER = SECURE_PAGE REFERER = SECURE_PAGE
headers = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows " 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" "NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"
" GTB7.1 (.NET CLR 3.5.30729)", " GTB7.1 (.NET CLR 3.5.30729)",
"Referer": REFERER, "Referer": REFERER,
} }
headers_ajax = {"User-Agent": "Mozilla/5.0 (Windows; U; Windows " 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" "NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8"
" GTB7.1 (.NET CLR 3.5.30729)", " GTB7.1 (.NET CLR 3.5.30729)",
"Accept": "application/json", "Accept": "application/json",
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
"X-Request": "JSON", "X-Request": "JSON",
"X-Brs-Xhr-Request": "true", "X-Brs-Xhr-Request": "true",
"X-Brs-Xhr-Schema": "DATA+OUT", "X-Brs-Xhr-Schema": "DATA+OUT",
"Referer": REFERER, "Referer": REFERER,
} }
def on_loaded(self): def on_loaded(self):
pass pass
def authenticate(self): @classmethod
url = "https://" + self.browser.DOMAIN + "/ajax/banque/otp.phtml" def authenticate(cls, browser):
data = "authentificationforteToken=%s&authentificationforteStep=otp&alertType=10100&org=%s&otp=%s&validate=" % (self.browser.auth_token, self.REFERER, self.browser.config['pin_code'].get()) browser.logger.info('Using the PIN Code %s to login', browser.auth_token)
req = urllib2.Request(url, data, self.headers_ajax) url = "https://" + browser.DOMAIN + "/ajax/banque/otp.phtml"
response = self.browser.open(req) data = "authentificationforteToken=%s&authentificationforteStep=otp&alertType=10100&org=%s&otp=%s&validate=" % (browser.auth_token, cls.REFERER, browser.config['pin_code'].get())
req = urllib2.Request(url, data, cls.headers_ajax)
browser.open(req)
url = "%s?" % (self.SECURE_PAGE) url = "%s?" % (cls.SECURE_PAGE)
data = "org=/&device=%s" % (self.browser.config['device'].get()) data = "org=/&device=%s" % (browser.config['device'].get())
req = urllib2.Request(url, data, headers=self.headers) req = urllib2.Request(url, data, headers=cls.headers)
response = self.browser.open(req) browser.open(req)
self.browser.auth_token = None browser.auth_token = None
def send_sms(self): def send_sms(self):
"""This function simulates the registration of a device on """This function simulates the registration of a device on
@ -71,7 +75,6 @@ class AuthenticationPage(Page):
I I
@param device device name to register @param device device name to register
@exception BrowserAuthenticationCodeMaxLimit when daily limit is consumed @exception BrowserAuthenticationCodeMaxLimit when daily limit is consumed
@exception BrowserIncorrectAuthenticationCode when code is not correct
""" """
url = "https://%s/ajax/banque/otp.phtml?org=%s&alertType=10100" % (self.browser.DOMAIN, self.REFERER) url = "https://%s/ajax/banque/otp.phtml?org=%s&alertType=10100" % (self.browser.DOMAIN, self.REFERER)
req = urllib2.Request(url, headers=self.headers_ajax) req = urllib2.Request(url, headers=self.headers_ajax)
@ -82,8 +85,7 @@ class AuthenticationPage(Page):
regex = re.compile(self.MAX_LIMIT) regex = re.compile(self.MAX_LIMIT)
r = regex.search(info) r = regex.search(info)
if r: if r:
self.logger.info("Boursorama - Vous avez atteint le nombre maximum d'utilisation de l'authentification forte") raise BrowserAuthenticationCodeMaxLimit("Vous avez atteint le nombre maximum d'utilisation de l'authentification forte")
raise BrowserAuthenticationCodeMaxLimit()
regex = re.compile(r"name=\\\"authentificationforteToken\\\" " regex = re.compile(r"name=\\\"authentificationforteToken\\\" "
r"value=\\\"(?P<value>\w*?)\\\"") r"value=\\\"(?P<value>\w*?)\\\"")
@ -95,4 +97,4 @@ class AuthenticationPage(Page):
data = "authentificationforteToken=%s&authentificationforteStep=start&alertType=10100&org=%s&validate=" % (self.browser.auth_token, self.REFERER) data = "authentificationforteToken=%s&authentificationforteStep=start&alertType=10100&org=%s&validate=" % (self.browser.auth_token, self.REFERER)
req = urllib2.Request(url, data, self.headers_ajax) req = urllib2.Request(url, data, self.headers_ajax)
response = self.browser.open(req) response = self.browser.open(req)
raise BrowserToBeContinued('pin_code') raise BrowserQuestion(Value('pin_code', label='Enter the PIN Code'))

View file

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>. # along with weboob. If not, see <http://www.gnu.org/licenses/>.
from weboob.tools.value import Value
class BrowserIncorrectPassword(Exception): class BrowserIncorrectPassword(Exception):
pass pass
@ -39,11 +39,12 @@ class BrowserUnavailable(Exception):
pass pass
class BrowserToBeContinued(BrowserUnavailable): class BrowserQuestion(BrowserIncorrectPassword):
def __init__(self, *args): """
self.fields = [] When raised by a browser,
for arg in args: """
self.fields.append(Value(label=arg)) def __init__(self, *fields):
self.fields = fields
class BrowserHTTPNotFound(BrowserUnavailable): class BrowserHTTPNotFound(BrowserUnavailable):

View file

@ -32,7 +32,7 @@ from weboob.capabilities.account import CapAccount, Account, AccountRegisterErro
from weboob.core.backendscfg import BackendAlreadyExists from weboob.core.backendscfg import BackendAlreadyExists
from weboob.core.modules import ModuleLoadError from weboob.core.modules import ModuleLoadError
from weboob.core.repositories import ModuleInstallError, IProgress from weboob.core.repositories import ModuleInstallError, IProgress
from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, BrowserForbidden, BrowserSSLError, BrowserToBeContinued from weboob.exceptions import BrowserUnavailable, BrowserIncorrectPassword, BrowserForbidden, BrowserSSLError, BrowserQuestion
from weboob.tools.value import Value, ValueBool, ValueFloat, ValueInt, ValueBackendPassword from weboob.tools.value import Value, ValueBool, ValueFloat, ValueInt, ValueBackendPassword
from weboob.tools.misc import to_unicode from weboob.tools.misc import to_unicode
from weboob.tools.ordereddict import OrderedDict from weboob.tools.ordereddict import OrderedDict
@ -547,7 +547,12 @@ class ConsoleApplication(Application):
This method can be overrided to support more exceptions types. This method can be overrided to support more exceptions types.
""" """
if isinstance(error, BrowserIncorrectPassword): if isinstance(error, BrowserQuestion):
for field in error.fields:
v = self.ask(field)
if v:
backend.config[field.id].set(v)
elif isinstance(error, BrowserIncorrectPassword):
msg = unicode(error) msg = unicode(error)
if not msg: if not msg:
msg = 'invalid login/password.' msg = 'invalid login/password.'
@ -560,11 +565,6 @@ class ConsoleApplication(Application):
print(u'FATAL(%s): ' % backend.name + self.BOLD + '/!\ SERVER CERTIFICATE IS INVALID /!\\' + self.NC, file=self.stderr) print(u'FATAL(%s): ' % backend.name + self.BOLD + '/!\ SERVER CERTIFICATE IS INVALID /!\\' + self.NC, file=self.stderr)
elif isinstance(error, BrowserForbidden): elif isinstance(error, BrowserForbidden):
print(u'Error(%s): %s' % (backend.name, msg or 'Forbidden'), file=self.stderr) print(u'Error(%s): %s' % (backend.name, msg or 'Forbidden'), file=self.stderr)
elif isinstance(error, BrowserToBeContinued):
for field in error.fields:
v = self.ask(field)
if v:
backend.config[field.label].set(v)
elif isinstance(error, BrowserUnavailable): elif isinstance(error, BrowserUnavailable):
msg = unicode(error) msg = unicode(error)
if not msg: if not msg: