From cd099eac382bf66740d578d0ec10d1c5e1b92c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Sobucki?= Date: Thu, 27 May 2021 10:35:27 +0200 Subject: [PATCH] [carrefourbanque] Handle dsp2 login status code On the website, after login if the user uses strong authentication a pop-in will show up. This pop-in's content is generated on the fly using js and depends on a 'popin_dsp2' parameter code. We use this parameter code to raise exceptions with the appropriate corresponding messages. --- modules/carrefourbanque/browser.py | 48 +++++++++++++++++++++++++----- modules/carrefourbanque/pages.py | 27 ++++------------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/modules/carrefourbanque/browser.py b/modules/carrefourbanque/browser.py index 1b55860acf..b2621cf6bd 100644 --- a/modules/carrefourbanque/browser.py +++ b/modules/carrefourbanque/browser.py @@ -27,7 +27,7 @@ from woob.browser import LoginBrowser, URL, need_login, StatesMixin from woob.exceptions import ( BrowserIncorrectPassword, RecaptchaV2Question, BrowserUnavailable, - AuthMethodNotImplemented, + ActionNeeded, AuthMethodNotImplemented, ) from woob.capabilities.bank import Account from woob.tools.compat import basestring @@ -128,9 +128,7 @@ def do_login(self): self.page.enter_password(self.password) - if not self.home.is_here(): - if self.page.has_2fa(): - raise AuthMethodNotImplemented("L'authentification forte Clé Secure n'est pas prise en charge.") + if self.login.is_here(): error = self.page.get_error_message() # Sometimes some connections aren't able to login because of a # maintenance randomly occuring. @@ -140,11 +138,45 @@ def do_login(self): elif 'saisies ne correspondent pas à l\'identifiant' in error: raise BrowserIncorrectPassword(error) raise AssertionError('Unexpected error at login: "%s"' % error) - raise AssertionError('Unexpected error at login') - if self.login.is_here(): - # Check if the website asks for strong authentication with OTP - self.page.check_action_needed() + dsp2_auth_code = self.page.get_dsp2_auth_code() + # The dsp2 authentication code gives informations on which strong authentication + # method is used by the user (Clé Secure: in-app validation or otp by SMS) + # on blocked access to the account and unavailability of the service. + if dsp2_auth_code: + if dsp2_auth_code == 'authent_cc': + raise ActionNeeded( + "Authentifiez-vous depuis l'appli Carrefour Banque avec Clé Secure." + ) + elif dsp2_auth_code == 'enrolement_cc': + # On the website 'enrolement_cc' code corresponds to a pop-in in which the Clé Secure + # authentication method is advertised. The user is presented with instructions on how + # to install Clé Secure and he is also given the option to log in using otp by SMS. + # + # Unfortunately, every time the user logs in the pop-in will show up, so we have no way + # of knowing whether we need to perform otp by SMS or if it's just the advertisement for Clé Secure. + raise AuthMethodNotImplemented( + "L'authentification forte par SMS n'est pas prise en charge." + ) + elif dsp2_auth_code == 'cle_secure_locked': + raise ActionNeeded( + "A la suite de 3 tentatives d'authentification erronées, votre Clé Secure a été bloquée." + + ' Par mesure de sécurité, créez un nouveau code Clé Secure depuis votre appli Carrefour Banque.' + ) + elif dsp2_auth_code == 'service_indisponible': + raise BrowserUnavailable( + 'Le service est momentanément indisponible. Excusez-nous pour la gêne occasionnée.' + + ' Veuillez ré-essayer ultérieurement.' + ) + elif 'acces_bloque' in dsp2_auth_code: + raise ActionNeeded( + "L'accès à votre Espace Client a été bloqué pour des raisons de sécurité." + + ' Pour le débloquer, contactez le service client de Carrefour Banque.' + ) + else: + raise AssertionError('Unhandled dsp2 authentication code at login %s' % dsp2_auth_code) + + raise AssertionError('Unexpected error at login') @need_login def get_account_list(self): diff --git a/modules/carrefourbanque/pages.py b/modules/carrefourbanque/pages.py index c8594c1f5b..31352bfaff 100644 --- a/modules/carrefourbanque/pages.py +++ b/modules/carrefourbanque/pages.py @@ -28,7 +28,6 @@ from PIL import Image -from woob.tools.json import json from woob.browser.pages import HTMLPage, LoggedPage, pagination, JsonPage from woob.browser.elements import ListElement, TableElement, ItemElement, method, DictElement from woob.browser.filters.standard import ( @@ -40,7 +39,6 @@ from woob.capabilities.wealth import Investment from woob.capabilities.base import NotAvailable, empty from woob.tools.capabilities.bank.transactions import FrenchTransaction -from woob.exceptions import ActionNeeded class CarrefourBanqueKeyboard(object): @@ -150,28 +148,15 @@ def enter_password(self, password): form.submit() - def check_action_needed(self): - # The JavaScript variable 'tc_vars' is supposed to contain the 'user_login' value - # and 'user_login'='logged'. If there is no user_login and 'user_login'='unlogged', - # the customer has to validate an OTP by SMS. - raw_text = Regexp( - CleanText('//script[contains(text(), "tc_vars")]'), - r'var tc_vars = (\{[^]]+\})' - )(self.doc) - json_text = json.loads(raw_text) - if not json_text['user_ID'] and json_text['user_login'] == 'unlogged': - # The real message contains the user's phone number, so we send a generic message. - raise ActionNeeded( - "Veuillez vous connecter sur le site de Carrefour Banque pour " - + "recevoir un code par SMS afin d'accéder à votre Espace Client." - ) - raise AssertionError('Unhandled error: password submission failed and we are still on Login Page.') - def get_error_message(self): return CleanText('//div[@class="messages error"]', default=None)(self.doc) - def has_2fa(self): - return bool(self.doc.xpath('//div[@id="region_content_dsp2"]')) + def get_dsp2_auth_code(self): + return Regexp( + CleanText('//script[contains(text(), "popin_dsp2")]', replace=[('-', '_')]), + r'"popin_dsp2":"(.+)"', + default='' + )(self.doc) class MaintenancePage(HTMLPage): -- GitLab