From a4dbaf01c5fb2431c423827de581e1a3200f525b Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Fri, 1 Oct 2010 21:24:22 +0200 Subject: [PATCH] if password if expired, switch with the value of the new 'rotating_password' backend setting --- weboob/backends/bnporc/backend.py | 24 +++++++++++- weboob/backends/bnporc/browser.py | 39 ++++++++++++++++++- weboob/backends/bnporc/errors.py | 23 +++++++++++ weboob/backends/bnporc/pages/__init__.py | 4 +- weboob/backends/bnporc/pages/accounts_list.py | 9 +++++ weboob/backends/bnporc/pages/login.py | 29 +++++++++++++- 6 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 weboob/backends/bnporc/errors.py diff --git a/weboob/backends/bnporc/backend.py b/weboob/backends/bnporc/backend.py index 35ce67c7..36959529 100644 --- a/weboob/backends/bnporc/backend.py +++ b/weboob/backends/bnporc/backend.py @@ -22,6 +22,9 @@ from weboob.tools.backend import BaseBackend from .browser import BNPorc +__all__ = ['BNPorcBackend'] + + class BNPorcBackend(BaseBackend, ICapBank): NAME = 'bnporc' MAINTAINER = 'Romain Bignon' @@ -30,12 +33,29 @@ class BNPorcBackend(BaseBackend, ICapBank): LICENSE = 'GPLv3' DESCRIPTION = 'BNP Paribas french bank\' website' 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 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): for account in self.browser.get_accounts_list(): diff --git a/weboob/backends/bnporc/browser.py b/weboob/backends/bnporc/browser.py index 0173f632..be6568e5 100644 --- a/weboob/backends/bnporc/browser.py +++ b/weboob/backends/bnporc/browser.py @@ -18,23 +18,34 @@ from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.backends.bnporc import pages +from .errors import PasswordExpired + + +__all__ = ['BNPorc'] + -# Browser class BNPorc(BaseBrowser): DOMAIN = 'www.secure.bnpparibas.net' PROTOCOL = 'https' ENCODING = None # refer to the HTML encoding PAGES = {'.*identifiant=DOSSIER_Releves_D_Operation.*': pages.AccountsList, '.*SAF_ROP.*': pages.AccountHistory, + '.*Action=SAF_CHM.*': pages.ChangePasswordPage, '.*NS_AVEET.*': pages.AccountComing, '.*NS_AVEDP.*': pages.AccountPrelevement, '.*Action=DSP_VGLOBALE.*': pages.LoginPage, '.*type=homeconnex.*': pages.LoginPage, '.*layout=HomeConnexion.*': pages.ConfirmPage, + '.*SAF_CHM_VALID.*': pages.ConfirmPage, } 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): self.location('https://www.secure.bnpparibas.net/banque/portail/particulier/HomeConnexion?type=homeconnex') @@ -57,9 +68,35 @@ class BNPorc(BaseBrowser): raise BrowserIncorrectPassword() 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): if not self.is_on_page(pages.AccountsList): self.location('/NSFR?Action=DSP_VGLOBALE') + return self.page.get_list() def get_account(self, id): diff --git a/weboob/backends/bnporc/errors.py b/weboob/backends/bnporc/errors.py new file mode 100644 index 00000000..97ef146b --- /dev/null +++ b/weboob/backends/bnporc/errors.py @@ -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 diff --git a/weboob/backends/bnporc/pages/__init__.py b/weboob/backends/bnporc/pages/__init__.py index 8567bfa1..ab862814 100644 --- a/weboob/backends/bnporc/pages/__init__.py +++ b/weboob/backends/bnporc/pages/__init__.py @@ -19,9 +19,9 @@ from .accounts_list import AccountsList from .account_coming import AccountComing from .account_history import AccountHistory -from .login import LoginPage, ConfirmPage +from .login import LoginPage, ConfirmPage, ChangePasswordPage class AccountPrelevement(AccountsList): pass __all__ = ['AccountsList', 'AccountComing', 'AccountHistory', 'LoginPage', - 'ConfirmPage', 'AccountPrelevement'] + 'ConfirmPage', 'AccountPrelevement', 'ChangePasswordPage'] diff --git a/weboob/backends/bnporc/pages/accounts_list.py b/weboob/backends/bnporc/pages/accounts_list.py index d50d5976..9c5183c7 100644 --- a/weboob/backends/bnporc/pages/accounts_list.py +++ b/weboob/backends/bnporc/pages/accounts_list.py @@ -21,6 +21,8 @@ import re from weboob.capabilities.bank import Account from weboob.tools.browser import BasePage +from ..errors import PasswordExpired + class AccountsList(BasePage): LINKID_REGEXP = re.compile(".*ch4=(\w+).*") @@ -59,4 +61,11 @@ class AccountsList(BasePage): account.coming = float(coming) 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 diff --git a/weboob/backends/bnporc/pages/login.py b/weboob/backends/bnporc/pages/login.py index ffbfd464..905654d8 100644 --- a/weboob/backends/bnporc/pages/login.py +++ b/weboob/backends/bnporc/pages/login.py @@ -18,10 +18,16 @@ from weboob.tools.mech import ClientForm import sys +import urllib +from logging import error from weboob.tools.browser import BasePage from weboob.backends.bnporc.captcha import Captcha, TileError + +__all__ = ['LoginPage', 'ConfirmPage', 'ChangePasswordPage'] + + class LoginPage(BasePage): def on_loaded(self): pass @@ -32,7 +38,7 @@ class LoginPage(BasePage): try: img.build_tiles() except TileError, err: - print >>sys.stderr, "Error: %s" % err + error("Error: %s" % err) if err.tile: err.tile.display() @@ -47,3 +53,24 @@ class LoginPage(BasePage): class ConfirmPage(BasePage): 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))