if password if expired, switch with the value of the new 'rotating_password' backend setting
This commit is contained in:
parent
7840a165e4
commit
a4dbaf01c5
6 changed files with 122 additions and 6 deletions
|
|
@ -22,6 +22,9 @@ from weboob.tools.backend import BaseBackend
|
||||||
from .browser import BNPorc
|
from .browser import BNPorc
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['BNPorcBackend']
|
||||||
|
|
||||||
|
|
||||||
class BNPorcBackend(BaseBackend, ICapBank):
|
class BNPorcBackend(BaseBackend, ICapBank):
|
||||||
NAME = 'bnporc'
|
NAME = 'bnporc'
|
||||||
MAINTAINER = 'Romain Bignon'
|
MAINTAINER = 'Romain Bignon'
|
||||||
|
|
@ -30,12 +33,29 @@ class BNPorcBackend(BaseBackend, ICapBank):
|
||||||
LICENSE = 'GPLv3'
|
LICENSE = 'GPLv3'
|
||||||
DESCRIPTION = 'BNP Paribas french bank\' website'
|
DESCRIPTION = 'BNP Paribas french bank\' website'
|
||||||
CONFIG = {'login': BaseBackend.ConfigField(description='Account ID'),
|
CONFIG = {'login': BaseBackend.ConfigField(description='Account ID'),
|
||||||
'password': BaseBackend.ConfigField(description='Password of account', is_masked=True)
|
'password': BaseBackend.ConfigField(description='Password of account', is_masked=True),
|
||||||
|
'rotating_password': BaseBackend.ConfigField(
|
||||||
|
description='Password to set when the allowed uses are exhausted (6 digits)',
|
||||||
|
default='',
|
||||||
|
regexp='^(\d{6}|)$'),
|
||||||
}
|
}
|
||||||
BROWSER = BNPorc
|
BROWSER = BNPorc
|
||||||
|
|
||||||
def create_default_browser(self):
|
def create_default_browser(self):
|
||||||
return self.create_browser(self.config['login'], self.config['password'])
|
if self.config['rotating_password'].isdigit() and len(self.config['rotating_password']) == 6:
|
||||||
|
rotating_password = self.config['rotating_password']
|
||||||
|
else:
|
||||||
|
rotating_password = None
|
||||||
|
return self.create_browser(self.config['login'],
|
||||||
|
self.config['password'],
|
||||||
|
password_changed_cb=self._password_changed_cb,
|
||||||
|
rotating_password=rotating_password)
|
||||||
|
|
||||||
|
def _password_changed_cb(self, old, new):
|
||||||
|
new_settings = {'password': new,
|
||||||
|
'rotating_password': old,
|
||||||
|
}
|
||||||
|
self.weboob.backends_config.edit_backend(self.name, self.NAME, new_settings)
|
||||||
|
|
||||||
def iter_accounts(self):
|
def iter_accounts(self):
|
||||||
for account in self.browser.get_accounts_list():
|
for account in self.browser.get_accounts_list():
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,34 @@
|
||||||
|
|
||||||
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
|
from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword
|
||||||
from weboob.backends.bnporc import pages
|
from weboob.backends.bnporc import pages
|
||||||
|
from .errors import PasswordExpired
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['BNPorc']
|
||||||
|
|
||||||
|
|
||||||
# Browser
|
|
||||||
class BNPorc(BaseBrowser):
|
class BNPorc(BaseBrowser):
|
||||||
DOMAIN = 'www.secure.bnpparibas.net'
|
DOMAIN = 'www.secure.bnpparibas.net'
|
||||||
PROTOCOL = 'https'
|
PROTOCOL = 'https'
|
||||||
ENCODING = None # refer to the HTML encoding
|
ENCODING = None # refer to the HTML encoding
|
||||||
PAGES = {'.*identifiant=DOSSIER_Releves_D_Operation.*': pages.AccountsList,
|
PAGES = {'.*identifiant=DOSSIER_Releves_D_Operation.*': pages.AccountsList,
|
||||||
'.*SAF_ROP.*': pages.AccountHistory,
|
'.*SAF_ROP.*': pages.AccountHistory,
|
||||||
|
'.*Action=SAF_CHM.*': pages.ChangePasswordPage,
|
||||||
'.*NS_AVEET.*': pages.AccountComing,
|
'.*NS_AVEET.*': pages.AccountComing,
|
||||||
'.*NS_AVEDP.*': pages.AccountPrelevement,
|
'.*NS_AVEDP.*': pages.AccountPrelevement,
|
||||||
'.*Action=DSP_VGLOBALE.*': pages.LoginPage,
|
'.*Action=DSP_VGLOBALE.*': pages.LoginPage,
|
||||||
'.*type=homeconnex.*': pages.LoginPage,
|
'.*type=homeconnex.*': pages.LoginPage,
|
||||||
'.*layout=HomeConnexion.*': pages.ConfirmPage,
|
'.*layout=HomeConnexion.*': pages.ConfirmPage,
|
||||||
|
'.*SAF_CHM_VALID.*': pages.ConfirmPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
is_logging = False
|
is_logging = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.rotating_password = kwargs.pop('rotating_password', None)
|
||||||
|
self.password_changed_cb = kwargs.pop('password_changed_cb', None)
|
||||||
|
BaseBrowser.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def home(self):
|
def home(self):
|
||||||
self.location('https://www.secure.bnpparibas.net/banque/portail/particulier/HomeConnexion?type=homeconnex')
|
self.location('https://www.secure.bnpparibas.net/banque/portail/particulier/HomeConnexion?type=homeconnex')
|
||||||
|
|
||||||
|
|
@ -57,9 +68,35 @@ class BNPorc(BaseBrowser):
|
||||||
raise BrowserIncorrectPassword()
|
raise BrowserIncorrectPassword()
|
||||||
self.is_logging = False
|
self.is_logging = False
|
||||||
|
|
||||||
|
def change_password(self, new_password):
|
||||||
|
assert new_password.isdigit() and len(new_password) == 6
|
||||||
|
|
||||||
|
self.location('https://www.secure.bnpparibas.net/SAF_CHM?Action=SAF_CHM')
|
||||||
|
assert self.is_on_page(pages.ChangePasswordPage)
|
||||||
|
|
||||||
|
self.page.change_password(self.password, new_password)
|
||||||
|
self.password, self.rotating_password = (new_password, self.password)
|
||||||
|
|
||||||
|
if self.password_changed_cb:
|
||||||
|
self.password_changed_cb(self.rotating_password, self.password)
|
||||||
|
|
||||||
|
def check_expired_password(func):
|
||||||
|
def inner(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
except PasswordExpired:
|
||||||
|
if self.rotating_password is not None:
|
||||||
|
self.change_password(self.rotating_password)
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return inner
|
||||||
|
|
||||||
|
@check_expired_password
|
||||||
def get_accounts_list(self):
|
def get_accounts_list(self):
|
||||||
if not self.is_on_page(pages.AccountsList):
|
if not self.is_on_page(pages.AccountsList):
|
||||||
self.location('/NSFR?Action=DSP_VGLOBALE')
|
self.location('/NSFR?Action=DSP_VGLOBALE')
|
||||||
|
|
||||||
return self.page.get_list()
|
return self.page.get_list()
|
||||||
|
|
||||||
def get_account(self, id):
|
def get_account(self, id):
|
||||||
|
|
|
||||||
23
weboob/backends/bnporc/errors.py
Normal file
23
weboob/backends/bnporc/errors.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright(C) 2009-2010 Romain Bignon
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['PasswordExpired']
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordExpired(Exception):
|
||||||
|
pass
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
from .accounts_list import AccountsList
|
from .accounts_list import AccountsList
|
||||||
from .account_coming import AccountComing
|
from .account_coming import AccountComing
|
||||||
from .account_history import AccountHistory
|
from .account_history import AccountHistory
|
||||||
from .login import LoginPage, ConfirmPage
|
from .login import LoginPage, ConfirmPage, ChangePasswordPage
|
||||||
|
|
||||||
class AccountPrelevement(AccountsList): pass
|
class AccountPrelevement(AccountsList): pass
|
||||||
|
|
||||||
__all__ = ['AccountsList', 'AccountComing', 'AccountHistory', 'LoginPage',
|
__all__ = ['AccountsList', 'AccountComing', 'AccountHistory', 'LoginPage',
|
||||||
'ConfirmPage', 'AccountPrelevement']
|
'ConfirmPage', 'AccountPrelevement', 'ChangePasswordPage']
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import re
|
||||||
from weboob.capabilities.bank import Account
|
from weboob.capabilities.bank import Account
|
||||||
from weboob.tools.browser import BasePage
|
from weboob.tools.browser import BasePage
|
||||||
|
|
||||||
|
from ..errors import PasswordExpired
|
||||||
|
|
||||||
class AccountsList(BasePage):
|
class AccountsList(BasePage):
|
||||||
LINKID_REGEXP = re.compile(".*ch4=(\w+).*")
|
LINKID_REGEXP = re.compile(".*ch4=(\w+).*")
|
||||||
|
|
||||||
|
|
@ -59,4 +61,11 @@ class AccountsList(BasePage):
|
||||||
account.coming = float(coming)
|
account.coming = float(coming)
|
||||||
|
|
||||||
l.append(account)
|
l.append(account)
|
||||||
|
|
||||||
|
if len(l) == 0:
|
||||||
|
# oops, no accounts? check if we have not exhausted the allowed use
|
||||||
|
# of this password
|
||||||
|
for div in self.document.getroot().cssselect('div.Style_texte_gras'):
|
||||||
|
if div.text.strip() == 'Vous avez atteint la date de fin de vie de votre code secret.':
|
||||||
|
raise PasswordExpired(div.text.strip())
|
||||||
return l
|
return l
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,16 @@
|
||||||
|
|
||||||
from weboob.tools.mech import ClientForm
|
from weboob.tools.mech import ClientForm
|
||||||
import sys
|
import sys
|
||||||
|
import urllib
|
||||||
|
from logging import error
|
||||||
|
|
||||||
from weboob.tools.browser import BasePage
|
from weboob.tools.browser import BasePage
|
||||||
from weboob.backends.bnporc.captcha import Captcha, TileError
|
from weboob.backends.bnporc.captcha import Captcha, TileError
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['LoginPage', 'ConfirmPage', 'ChangePasswordPage']
|
||||||
|
|
||||||
|
|
||||||
class LoginPage(BasePage):
|
class LoginPage(BasePage):
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
pass
|
pass
|
||||||
|
|
@ -32,7 +38,7 @@ class LoginPage(BasePage):
|
||||||
try:
|
try:
|
||||||
img.build_tiles()
|
img.build_tiles()
|
||||||
except TileError, err:
|
except TileError, err:
|
||||||
print >>sys.stderr, "Error: %s" % err
|
error("Error: %s" % err)
|
||||||
if err.tile:
|
if err.tile:
|
||||||
err.tile.display()
|
err.tile.display()
|
||||||
|
|
||||||
|
|
@ -47,3 +53,24 @@ class LoginPage(BasePage):
|
||||||
|
|
||||||
class ConfirmPage(BasePage):
|
class ConfirmPage(BasePage):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class ChangePasswordPage(BasePage):
|
||||||
|
def change_password(self, current, new):
|
||||||
|
img = Captcha(self.browser.openurl('/NSImgGrille'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
img.build_tiles()
|
||||||
|
except TileError, err:
|
||||||
|
error('Error: %s' % err)
|
||||||
|
if err.tile:
|
||||||
|
err.tile.display()
|
||||||
|
|
||||||
|
code_current = img.get_codes(current)
|
||||||
|
code_new = img.get_codes(new)
|
||||||
|
|
||||||
|
data = {'ch1': code_current,
|
||||||
|
'ch2': code_new,
|
||||||
|
'ch3': code_new
|
||||||
|
}
|
||||||
|
|
||||||
|
self.browser.location('/SAF_CHM_VALID', urllib.urlencode(data))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue