The new woob repository is here: https://gitlab.com/woob/woob. This gitlab will be removed soon.

Commit 51af15e3 authored by Vincent A's avatar Vincent A

backport devel modules fixes

parent 8e235d43
Pipeline #3716 passed with stages
in 64 minutes and 57 seconds
......@@ -26,14 +26,15 @@
from woob.exceptions import (
BrowserIncorrectPassword, BrowserUnavailable, ImageCaptchaQuestion, BrowserQuestion,
WrongCaptchaResponse, NeedInteractiveFor2FA, BrowserPasswordExpired,
AppValidation, AppValidationExpired,
AppValidation, AppValidationExpired, AppValidationCancelled,
)
from woob.tools.value import Value
from woob.browser.browsers import ClientError
from .pages import (
LoginPage, SubscriptionsPage, DocumentsPage, DownloadDocumentPage, HomePage,
PanelPage, SecurityPage, LanguagePage, HistoryPage, PasswordExpired, ApprovalPage,
SecurityPage, LanguagePage, HistoryPage, PasswordExpired, ApprovalPage, PollingPage,
ResetPasswordPage,
)
......@@ -54,17 +55,22 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
WRONG_CAPTCHA_RESPONSE = "Saisissez les caractères tels qu'ils apparaissent sur l'image."
login = URL(r'/ap/signin(.*)', LoginPage)
home = URL(r'/$', r'/\?language=\w+$', HomePage)
panel = URL('/gp/css/homepage.html/ref=nav_youraccount_ya', PanelPage)
subscriptions = URL(r'/ap/cnep(.*)', SubscriptionsPage)
history = URL(r'/gp/your-account/order-history\?ref_=ya_d_c_yo', HistoryPage)
home = URL(r'/$', r'/\?language=.+$', HomePage)
subscriptions = URL(r'/gp/profile', SubscriptionsPage)
history = URL(
r'/gp/your-account/order-history\?ref_=ya_d_c_yo',
r'/gp/css/order-history\?',
HistoryPage,
)
documents = URL(
r'/gp/your-account/order-history\?opt=ab&digitalOrders=1(.*)&orderFilter=year-(?P<year>.*)',
r'/gp/your-account/order-history',
DocumentsPage,
)
download_doc = URL(r'/gp/shared-cs/ajax/invoice/invoice.html', DownloadDocumentPage)
approval_page = URL(r'/ap/cvf/approval', ApprovalPage)
approval_page = URL(r'/ap/cvf/approval\?', ApprovalPage)
reset_password_page = URL(r'/ap/forgotpassword/reverification', ResetPasswordPage)
poll_page = URL(r'/ap/cvf/approval/poll', PollingPage)
security = URL(
r'/ap/dcq',
r'/ap/cvf/',
......@@ -76,7 +82,9 @@ class AmazonBrowser(LoginBrowser, StatesMixin):
__states__ = ('otp_form', 'otp_url', 'otp_style', 'otp_headers')
STATE_DURATION = 10
# According to the cookies we are fine for 1 year after the last sync.
# If we reset the state every 10 minutes we'll get a in-app validation after 10 minutes
STATE_DURATION = 60 * 24 * 365
otp_form = None
otp_url = None
......@@ -156,27 +164,39 @@ def handle_captcha(self, captcha):
raise ImageCaptchaQuestion(image)
def check_app_validation(self):
# client has 60 seconds to unlock this page
# the resend link will appear from 60 seconds is why there are 2 additional seconds, it's to have a margin
timeout = time.time() + 62.00
second_try = True
# 25' on website, we don't wait that much, but leave sufficient time for the user
timeout = time.time() + 600.00
app_validation_link = self.page.get_link_app_validation()
polling_request = self.page.get_polling_request()
approval_status = ''
while time.time() < timeout:
link = self.page.get_link_app_validation()
self.location(link)
if self.approval_page.is_here():
time.sleep(2)
else:
return
self.location(polling_request)
approval_status = self.page.get_approval_status()
if time.time() >= timeout and second_try:
# second try because 60 seconds is short, the second try is longger
second_try = False
timeout = time.time() + 70.00
self.page.resend_link()
if approval_status != 'TransactionPending':
break
# poll every 5 seconds on website
time.sleep(5)
else:
raise AppValidationExpired()
if approval_status in ['TransactionCompleted', 'TransactionResponded']:
self.location(app_validation_link)
if self.reset_password_page.is_here():
raise AppValidationCancelled()
if self.approval_page.is_here():
raise AssertionError('The validation was not effective for an unknown reason.')
elif approval_status == 'TransactionCompletionTimeout':
raise AppValidationExpired()
else:
raise AssertionError('Unknown transaction status: %s' % approval_status)
def do_login(self):
if self.config['pin_code'].get():
# Resolve pin_code
......@@ -279,18 +299,10 @@ def to_english(self, language):
@need_login
def iter_subscription(self):
self.location(self.panel.go().get_sub_link())
if self.home.is_here():
if self.page.get_login_link():
self.is_login()
self.location(self.page.get_panel_link())
elif not self.subscriptions.is_here():
self.is_login()
self.subscriptions.go()
# goes back to the subscription page as you may be redirected to the documents page
if not self.subscriptions.is_here():
self.location(self.panel.go().get_sub_link())
self.is_login()
yield self.page.get_item()
......
......@@ -23,28 +23,24 @@
from woob.browser.elements import ItemElement, ListElement, method
from woob.browser.filters.html import Link, Attr
from woob.browser.filters.standard import (
CleanText, CleanDecimal, Env, Regexp, Format,
Field, Currency, RegexpError, Date, Async, AsyncLoad,
CleanText, CleanDecimal, Env, Regexp, Format, RawText,
Field, Currency, Date, Async, AsyncLoad,
Coalesce,
)
from woob.capabilities.bill import DocumentTypes, Bill, Subscription
from woob.capabilities.base import NotAvailable
from woob.tools.json import json
from woob.tools.date import parse_french_date
class HomePage(HTMLPage):
def get_login_link(self):
return self.doc.xpath('//a[./span[contains(., "%s")]]/@href' % self.browser.L_SIGNIN)[0]
return Attr('//a[@data-nav-role="signin"]', 'href')(self.doc)
def get_panel_link(self):
return Link('//a[contains(@href, "homepage.html") and has-class(@nav-link)]')(self.doc)
class PanelPage(LoggedPage, HTMLPage):
def get_sub_link(self):
return CleanText('//a[@class="ya-card__whole-card-link" and contains(@href, "cnep")]/@href')(self.doc)
class SecurityPage(HTMLPage):
def get_otp_type(self):
if self.doc.xpath('//form[@id="auth-select-device-form"]'):
......@@ -108,18 +104,30 @@ def has_form_select_device(self):
class ApprovalPage(HTMLPage, LoggedPage):
def get_msg_app_validation(self):
msg = CleanText('//span[has-class("transaction-approval-word-break")]')
msg = CleanText('//div[has-class("a-spacing-large")]/span[has-class("transaction-approval-word-break")]')
sending_address = CleanText('//div[@class="a-row"][1]')
msg = Format('%s %s', msg, sending_address)
return msg(self.doc)
return Format('%s %s', msg, sending_address)(self.doc)
def get_link_app_validation(self):
return Link('//a[@id="resend-approval-link"]')(self.doc)
return Attr('//input[@name="openid.return_to"]', 'value')(self.doc)
def resend_link(self):
form = self.get_form(id='resend-approval-form')
form.submit()
def get_polling_request(self):
form = self.get_form(id="pollingForm")
return form.request
class PollingPage(HTMLPage):
def get_approval_status(self):
return Attr('//input[@name="transactionApprovalStatus"]', 'value', default=None)(self.doc)
class ResetPasswordPage(HTMLPage):
pass
class LanguagePage(HTMLPage):
pass
......@@ -165,14 +173,15 @@ class SubscriptionsPage(LoggedPage, HTMLPage):
class get_item(ItemElement):
klass = Subscription
def obj_subscriber(self):
try:
return Regexp(CleanText('//div[contains(@class, "a-fixed-right-grid-col")]'), self.page.browser.L_SUBSCRIBER)(self)
except RegexpError:
return self.page.browser.username
obj_id = 'amazon'
def obj_subscriber(self):
profile_data = json.loads(Regexp(
RawText('//script[contains(text(), "window.CustomerProfileRootProps")]'),
r'window.CustomerProfileRootProps = ({.+});',
)(self))
return profile_data.get('nameHeaderData', {}).get('name', NotAvailable)
def obj_label(self):
return self.page.browser.username
......@@ -219,7 +228,7 @@ def obj_date(self):
Date(CleanText('.//div[has-class("a-span2") and not(has-class("recipient"))]/div[2]'), parse_func=parse_french_date, dayfirst=True, default=NotAvailable),
)(self)
def obj_price(self):
def obj_total_price(self):
# Some orders, audiobooks for example, are paid using "audio credits", they have no price or currency
currency = Env('currency')(self)
return CleanDecimal(
......@@ -245,10 +254,13 @@ def obj_url(self):
url = Coalesce(
Link('//a[contains(@href, "download")]|//a[contains(@href, "generated_invoices")]', default=NotAvailable),
Link('//a[contains(text(), "Récapitulatif de commande")]', default=NotAvailable),
default=NotAvailable
)(async_page.doc)
return url
def obj_format(self):
if not Field('url')(self):
return NotAvailable
if 'summary' in Field('url')(self):
return 'html'
return 'pdf'
......
......@@ -17,14 +17,18 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this woob module. If not, see <http://www.gnu.org/licenses/>.
# flake8: compatible
from __future__ import unicode_literals
import re
from datetime import date
from time import time
from dateutil.relativedelta import relativedelta
from woob.browser import LoginBrowser, URL, need_login
from woob.exceptions import ActionNeeded
from woob.exceptions import ActionNeeded, BrowserIncorrectPassword, BrowserUnavailable
from woob.tools.capabilities.bill.documents import merge_iterators
from .pages import (
......@@ -38,10 +42,23 @@ class AmeliBrowser(LoginBrowser):
BASEURL = 'https://assure.ameli.fr'
error_page = URL(r'/vu/INDISPO_COMPTE_ASSURES.html', ErrorPage)
login_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&connexioncompte_2actionEvt=afficher.*', LoginPage)
redirect_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&.*validationconnexioncompte.*', RedirectPage)
cgu_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page.*', CguPage)
subscription_page = URL(r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page.*', SubscriptionPage)
login_page = URL(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&connexioncompte_2actionEvt=afficher.*',
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&.*validationconnexioncompte.*',
LoginPage
)
redirect_page = URL(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&.*validationconnexioncompte.*',
RedirectPage
)
cgu_page = URL(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page.*',
CguPage
)
subscription_page = URL(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page.*',
SubscriptionPage
)
documents_details_page = URL(r'/PortailAS/paiements.do', DocumentsDetailsPage)
documents_first_summary_page = URL(
r'PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_releve_mensuel_paiement_page',
......@@ -59,6 +76,19 @@ def do_login(self):
_ct = self.ct_page.open(method='POST', headers={'FETCH-CSRF-TOKEN': '1'}).get_ct_value()
self.page.login(self.username, self.password, _ct)
if self.login_page.is_here():
err_msg = self.page.get_error_message()
wrongpass_regex = re.compile(
'numéro de sécurité sociale et le code personnel'
+ '|compte ameli verrouillé'
)
if wrongpass_regex.search(err_msg):
raise BrowserIncorrectPassword(err_msg)
raise AssertionError('Unhandled error at login %s' % err_msg)
if self.error_page.is_here():
raise BrowserUnavailable(self.page.get_error_message())
if self.cgu_page.is_here():
raise ActionNeeded(self.page.get_cgu_message())
......@@ -84,7 +114,7 @@ def _iter_details_documents(self, subscription):
'afficherRS': 'false',
'afficherReleves': 'false',
'afficherRentes': 'false',
'idNoCache': int(time()*1000)
'idNoCache': int(time() * 1000),
}
# website tell us details documents are available for 6 months
......@@ -105,8 +135,10 @@ def _iter_summary_documents(self, subscription):
for doc in self.page.iter_documents(subid=subscription.id):
yield doc
@need_login
def iter_documents(self, subscription):
for doc in merge_iterators(self._iter_details_documents(subscription), self._iter_summary_documents(subscription)):
for doc in merge_iterators(
self._iter_details_documents(subscription),
self._iter_summary_documents(subscription)
):
yield doc
......@@ -17,12 +17,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this woob module. If not, see <http://www.gnu.org/licenses/>.
# flake8: compatible
from __future__ import unicode_literals
from woob.capabilities.base import find_object
from woob.tools.backend import Module, BackendConfig
from woob.capabilities.bill import CapDocument, Document, DocumentTypes, SubscriptionNotFound, DocumentNotFound, Subscription
from woob.capabilities.bill import (
CapDocument, Document, DocumentNotFound, DocumentTypes,
Subscription, SubscriptionNotFound,
)
from woob.tools.value import ValueBackendPassword
from .browser import AmeliBrowser
......
......@@ -17,24 +17,28 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this woob module. If not, see <http://www.gnu.org/licenses/>.
# flake8: compatible
from __future__ import unicode_literals
import re
from hashlib import sha1
from woob.browser.elements import method, ListElement, ItemElement, DictElement
from woob.browser.filters.html import Link
from woob.browser.filters.standard import CleanText, Regexp, CleanDecimal, Currency, Field, Env, Format
from woob.browser.filters.standard import CleanText, Coalesce, Regexp, CleanDecimal, Currency, Field, Env, Format
from woob.browser.filters.json import Dict
from woob.browser.pages import LoggedPage, HTMLPage, PartialHTMLPage, RawPage, JsonPage
from woob.capabilities.bill import Subscription, Bill, Document, DocumentTypes
from woob.exceptions import BrowserUnavailable, BrowserIncorrectPassword
from woob.tools.compat import html_unescape
from woob.tools.date import parse_french_date
from woob.tools.json import json
class LoginPage(HTMLPage):
def is_here(self):
return self.doc.xpath('//form[contains(@id, "CompteForm")]')
def login(self, username, password, _ct):
form = self.get_form(id='connexioncompte_2connexionCompteForm')
form['connexioncompte_2numSecuriteSociale'] = username
......@@ -42,6 +46,13 @@ def login(self, username, password, _ct):
form['_ct'] = _ct
form.submit()
def get_error_message(self):
return Coalesce(
CleanText('//div[@id="loginPage"]//div[has-class("zone-alerte") and not(has-class("hidden"))]/span'),
CleanText('//div[@class="centrepage compte_bloque"]//p[@class="msg_erreur"]'),
default=None
)(self.doc)
class CtPage(RawPage):
# the page contains only _ct value
......@@ -53,19 +64,6 @@ class RedirectPage(LoggedPage, HTMLPage):
REFRESH_MAX = 0
REFRESH_XPATH = '//meta[@http-equiv="refresh"]'
def on_load(self):
if not self.doc.xpath('//meta[@http-equiv="refresh"]'):
error_message = self.get_error_message()
if 'Le numéro de sécurité sociale et le code personnel' in error_message:
raise BrowserIncorrectPassword(error_message)
raise AssertionError(error_message)
super(RedirectPage, self).on_load()
def get_error_message(self):
return CleanText(
'//div[@id="loginPage"]//div[has-class("zone-alerte") and not(has-class("hidden"))]/span'
)(self.doc)
class CguPage(LoggedPage, HTMLPage):
def get_cgu_message(self):
......@@ -73,9 +71,8 @@ def get_cgu_message(self):
class ErrorPage(HTMLPage):
def on_load(self):
msg = CleanText('//div[@id="backgroundId"]//p')(self.doc)
raise BrowserUnavailable(msg)
def get_error_message(self):
return html_unescape(CleanText('//div[@class="mobile"]/p')(self.doc))
class SubscriptionPage(LoggedPage, HTMLPage):
......@@ -115,7 +112,9 @@ def obj_id(self):
obj_format = 'pdf'
def obj_date(self):
year = Regexp(CleanText('./preceding-sibling::li[@class="rowdate"]//span[@class="mois"]'), r'(\d+)')(self)
year = Regexp(
CleanText('./preceding-sibling::li[@class="rowdate"]//span[@class="mois"]'), r'(\d+)'
)(self)
day_month = CleanText('.//div[has-class("col-date")]/span')(self)
return parse_french_date(day_month + ' ' + year)
......
This diff is collapsed.
This diff is collapsed.
......@@ -20,7 +20,7 @@
from woob.capabilities.bank import CapBank
from woob.tools.backend import Module, BackendConfig
from woob.tools.value import ValueBackendPassword
from woob.tools.value import ValueBackendPassword, ValueTransient
from .browser import AmericanExpressBrowser
......@@ -37,12 +37,15 @@ class AmericanExpressModule(Module, CapBank):
LICENSE = 'LGPLv3+'
CONFIG = BackendConfig(
ValueBackendPassword('login', label='Code utilisateur', masked=False),
ValueBackendPassword('password', label='Mot de passe')
ValueBackendPassword('password', label='Mot de passe'),
ValueTransient('request_information'),
ValueTransient('otp', regexp=r'^\d{6}$'),
)
BROWSER = AmericanExpressBrowser
def create_default_browser(self):
return self.create_browser(
self.config,
self.config['login'].get(),
self.config['password'].get()
)
......
......@@ -20,9 +20,8 @@
from __future__ import unicode_literals
from decimal import Decimal
import re
from woob.browser.pages import LoggedPage, JsonPage, HTMLPage, RawPage
from woob.browser.pages import LoggedPage, JsonPage, HTMLPage
from woob.browser.elements import ItemElement, DictElement, method
from woob.browser.filters.standard import (
Date, Eval, Env, CleanText, Field, CleanDecimal, Format,
......@@ -77,6 +76,7 @@ def get_error_code(self):
# - LGON005 = Account blocked
# - LGON008 = ?
# - LGON010 = Browser unavailable
# - LGON013 = SCA
return CleanText(Dict('errorCode'))(self.doc)
def get_error_message(self):
......@@ -88,6 +88,27 @@ def get_error_message(self):
def get_redirect_url(self):
return CleanText(Dict('redirectUrl'))(self.doc)
def get_reauth(self):
return Dict('reauth')(self.doc)
class ReadAuthChallengePage(JsonPage):
def get_challenge(self):
return Dict("challenge")(self.doc)
def get_account_token(self):
identity_data = Dict("identityData")(self.doc)
assert len(identity_data) == 1, "How can we have multiple identity_data?"
return identity_data[0]["identityValue"]
def get_otp_methods(self):
return Dict("tenuredChannels")(self.doc)
class UpdateAuthTokenPage(JsonPage):
def get_pending_challenges(self):
return Dict('pendingChallenges')(self.doc)
class AccountsPage(LoggedPage, JsonPage):
@method
......@@ -213,10 +234,3 @@ def obj_original_amount(self):
return original_amount
obj__ref = Dict('identifier')
class JsDataPage(RawPage):
def get_version(self):
version = re.search(r'"(\d\.[\d\._]+)"', self.text)
assert version, 'Could not match version number in javascript'
return version.group(1)
......@@ -48,7 +48,7 @@ class AprilModule(Module, CapDocument, CapProfile):
MAINTAINER = "Ludovic LANGE"
EMAIL = "llange@users.noreply.github.com"
LICENSE = "LGPLv3+"
VERSION = "3.0"
VERSION = '3.0'
BROWSER = AprilBrowser
......
......@@ -50,7 +50,7 @@
NewLoginPage, JsFilePage, AuthorizePage, LoginTokensPage, VkImagePage,
AuthenticationMethodPage, AuthenticationStepPage, CaissedepargneVirtKeyboard,
AccountsNextPage, GenericAccountsPage, InfoTokensPage, NatixisUnavailablePage,
RedirectErrorPage,
RedirectErrorPage, BPCEPage,
)
from .document_pages import BasicTokenPage, SubscriberPage, SubscriptionsPage, DocumentsPage
from .linebourse_browser import LinebourseAPIBrowser
......@@ -115,6 +115,7 @@ def wrapper(browser, *args, **kwargs):
class BanquePopulaire(LoginBrowser):
first_login_page = URL(r'/$')
login_page = URL(r'https://[^/]+/auth/UI/Login.*', LoginPage)
new_login = URL(r'https://[^/]+/.*se-connecter/sso', NewLoginPage)
js_file = URL(r'https://[^/]+/.*se-connecter/main-.*.js$', JsFilePage)
......@@ -206,6 +207,7 @@ class BanquePopulaire(LoginBrowser):
)
redirect_page = URL(r'https://[^/]+/portailinternet/_layouts/Ibp.Cyi.Layouts/RedirectSegment.aspx.*', RedirectPage)
bpce_page = URL(r'https://[^/]+/cyber/ibp/ate/portal/internet89C3Portal.jsp', BPCEPage)
redirect_error_page = URL(
r'https://[^/]+/portailinternet/?$',
......@@ -218,6 +220,7 @@ class BanquePopulaire(LoginBrowser):
r'https://[^/]+/portailinternet/Pages/[dD]efault.aspx',
r'https://[^/]+/portailinternet/Transactionnel/Pages/CyberIntegrationPage.aspx',
r'https://[^/]+/cyber/internet/ShowPortal.do\?token=.*',
r'https://[^/]+/cyber/internet/ShowPortal.do\?taskInfoOID=.*',
HomePage
)
......@@ -326,7 +329,7 @@ def follow_back_button_if_any(self, params=None, actions=None):
@no_need_login
def do_login(self):
try:
self.location(self.BASEURL)
self.first_login_page.go()
except (ClientError, HTTPNotFound) as e:
if e.response.status_code in (403, 404):
# Sometimes the website makes some redirections that leads
......
......@@ -203,6 +203,10 @@ def build_doc(self, data, *args, **kwargs):
return super(MyHTMLPage, self).build_doc(data, *args, **kwargs)
class BPCEPage(LoggedPage, MyHTMLPage):
pass
class RedirectPage(LoggedPage, MyHTMLPage):
ENCODING = None
......@@ -852,7 +856,7 @@ def iter_accounts(self, next_pages, accounts_parsed=None, next_with_params=None)
account.balance = Decimal(balance or '0.0')
account.currency = currency or Account.get_currency(balance_text)
if account.type == account.TYPE_LOAN:
if account.type in (Account.TYPE_LOAN, Account.TYPE_REVOLVING_CREDIT):
account.balance = - abs(account.balance)
account._prev_debit = None
......
......@@ -166,7 +166,6 @@ class item(ItemElement):
)
obj_number = obj_id
obj_label = CleanText('./td//div[contains(@class, "-synthese-title")]')
obj_balance = MyDecimal('./td//div[contains(@class, "-synthese-num")]', replace_dots=True)
obj_currency = FrenchTransaction.Currency('./td//div[contains(@class, "-synthese-num")]')
obj_type = Map(Regexp(Field('label'), r'^([^ ]*)'), TYPE, default=Account.TYPE_UNKNOWN)
......@@ -175,6 +174,13 @@ def obj_url(self):
obj__card_balance = CleanDecimal('./td//div[@class="synthese-encours"][last()]/div[2]', default=None)
def obj_balance(self):
if Field('type')(self) == Account.TYPE_LOAN:
sign = '-'
else:
sign = None
return MyDecimal('./td//div[contains(@class, "-synthese-num")]', replace_dots=True, sign=sign)(self)
def condition(self):
return not len(self.el.xpath('./td[@class="chart"]'))
......
......@@ -38,14 +38,15 @@
from woob.tools.decorators import retry
from woob.tools.capabilities.bank.bank_transfer import sorted_transfers
from woob.tools.capabilities.bank.transactions import sorted_transactions
from woob.browser.exceptions import ServerError
from woob.browser.exceptions import ServerError, ClientError
from woob.browser.elements import DataError
from woob.exceptions import (
BrowserIncorrectPassword, BrowserUnavailable, AppValidation,
AppValidationExpired, ActionNeeded,
AppValidationExpired, ActionNeeded, BrowserUserBanned, BrowserPasswordExpired,
)
from woob.tools.value import Value
from woob.tools.capabilities.bank.investments import create_french_liquidity
from woob.browser.filters.standard import QueryValue
from .pages import (
LoginPage, AccountsPage, AccountsIBANPage, HistoryPage, TransferInitPage,
......@@ -55,7 +56,7 @@
RecipientsPage, ValidateTransferPage, RegisterTransferPage, AdvisorPage,
AddRecipPage, ActivateRecipPage, ProfilePage, ListDetailCardPage, ListErrorPage,
UselessPage, TransferAssertionError, LoanDetailsPage, TransfersPage, OTPPage,
UnavailablePage,
UnavailablePage, InitLoginPage, FinalizeLoginPage,
)
from .document_pages import DocumentsPage, TitulairePage, RIBPage
......@@ -65,31 +66,42 @@
class BNPParibasBrowser(LoginBrowser, StatesMixin):
TIMEOUT = 30.0
init_login = URL(
r'https://connexion-mabanque.bnpparibas/oidc/authorize',
InitLoginPage
)
login = URL(
r'identification-wspl-pres/identification\?acceptRedirection=true&timestamp=(?P<timestamp>\d+)',
r'SEEA-pa01/devServer/seeaserver',
r'https://mabanqueprivee.bnpparibas.net/fr/espace-prive/comptes-et-contrats\?u=%2FSEEA-pa01%2FdevServer%2Fseeaserver',
r'https://connexion-mabanque.bnpparibas/login',
LoginPage
)
finalize_login = URL(
r'SEEA-pa01/devServer/seeaserver',
FinalizeLoginPage
)
errors_list = URL(
r'/rsc/contrib/identification/src/zonespubliables/mabanque-part/fr/identification-fr-part-CAS.json'
)
list_error_page = URL(
r'https://mabanque.bnpparibas/rsc/contrib/document/properties/identification-fr-part-V1.json', ListErrorPage
)
useless_page = URL(r'/fr/connexion/comptes-et-contrats', UselessPage)
otp = URL(r'/fr/espace-prive/authentification-forte-anr', OTPPage)
otp = URL(
r'/fr/espace-prive/authentification-forte-anr',
r'https://.*/fr/secure/authentification-forte', # We can be redirected on other baseurl
OTPPage
)
con_threshold = URL(
r'/fr/connexion/100-connexions',
r'https://.*/100-connexion',
r'/fr/connexion/mot-de-passe-expire',
r'/fr/espace-prive/100-connexions.*',
r'/fr/espace-pro/100-connexions-pro.*',
r'/fr/espace-pro/changer-son-mot-de-passe',
r'/fr/espace-client/100-connexions',
r'/fr/espace-prive/mot-de-passe-expire',
r'/fr/client/mdp-expire',
r'/fr/client/100-connexion',
ConnectionThresholdPage
)
unavailable_page = URL(
......@@ -138,6 +150,8 @@ class BNPParibasBrowser(LoginBrowser, StatesMixin):
profile = URL(r'/kyc-wspl/rest/informationsClient', ProfilePage)
list_detail_card = URL(r'/udcarte-wspl/rest/listeDetailCartes', ListDetailCardPage)
DIST_ID = None
STATE_DURATION = 10
__states__ = ('rcpt_transfer_id',)
......@@ -158,19 +172,66 @@ def do_login(self):
if not (self.username.isdigit() and self.password.isdigit()):
raise BrowserIncorrectPassword()
timestamp = int(time.time() * 1e3)
# If a previous login session is still valid, we will be redirected with a
# 302 http status code. Otherwise, the page content will be returned directly.
# We have to avoid following redirects as there is a bug with bnpparibas
# website that could enter in a redirect loop if we try to go to the page
# more than once with an active session.