Commit bcd91c44 authored by Maxime Gasselin's avatar Maxime Gasselin Committed by Vincent A

[banquepopulaire] repair login for bpaca

"Phase" bpcesta params in authorize url is not necessary anymore for "part" bpaca user.
But for the other cases the param is still required. The phase information seems to be in
js file and the value is not hardcoded. That's why we must be vigilant if other subwebsites
migrate to this version. Moreover in bpaca the good user status is required. We can get the
information in API.
parent 6a5eefbe
......@@ -19,6 +19,7 @@
from __future__ import unicode_literals
import json
import re
from uuid import uuid4
......@@ -45,7 +46,7 @@ from .pages import (
LineboursePage, AlreadyLoginPage, InvestmentPage,
NewLoginPage, JsFilePage, AuthorizePage, LoginTokensPage, VkImagePage,
AuthenticationMethodPage, AuthenticationStepPage, CaissedepargneVirtKeyboard,
AccountsNextPage, GenericAccountsPage,
AccountsNextPage, GenericAccountsPage, InfoTokensPage,
from .document_pages import BasicTokenPage, SubscriberPage, SubscriptionsPage, DocumentsPage
......@@ -111,6 +112,8 @@ class BanquePopulaire(LoginBrowser):
js_file = URL(r'https://[^/]+/se-connecter/main-.*.js$', JsFilePage)
authorize = URL(r'', AuthorizePage)
login_tokens = URL(r'', LoginTokensPage)
info_tokens = URL(r'', InfoTokensPage)
user_info = URL(r'', InfoTokensPage)
authentication_step = URL(
r'<validation_id>[^/]+)/step', AuthenticationStepPage
......@@ -205,6 +208,7 @@ class BanquePopulaire(LoginBrowser):
documents_page = URL(r'/api-bp/wapi/2.0/abonnes/current/documents/recherche-avancee', DocumentsPage)
def __init__(self, website, *args, **kwargs): = website
self.BASEURL = 'https://%s' % website
# this url is required because the creditmaritime abstract uses an other url
if 'cmgo.creditmaritime' in self.BASEURL:
......@@ -301,24 +305,79 @@ class BanquePopulaire(LoginBrowser):
client_id =
nonce = # Hardcoded in their js...
data = {
'grant_type': 'client_credentials',
'scope': '',
# The 2 followings requests are needed in order to get
# user type (part, ent and pro)
headers = {'Authorization': 'Bearer %s' %}
data = {
'characteristics': {
'iTEntityType': {
'code': '03', # 03 for BP and 02 for CE
'label': 'BP',
'userCode': self.username,
'bankId': cdetab,
'subscribeTypeItems': [],
self.user_info.go(headers=headers, json=data)
self.user_type =
# On the website, this sends back json because of the header
# 'Accept': 'applcation/json'. If we do not add this header, we
# instead have a form that we can directly send to complete
# the login.
'nonce': nonce,
'scope': '',
'response_type': 'id_token token',
'response_mode': 'form_post',
'cdetab': cdetab,
'login_hint': self.username.upper(),
'display': 'page',
'client_id': client_id,
'claims': '{"userinfo":{"cdetab":null,"authMethod":null,"authLevel":null},"id_token":{"auth_time":{"essential":true},"last_login":null}}',
'bpcesta': '{"csid":"%s","typ_app":"rest","enseigne":"bp","typ_sp":"out-band","typ_act":"auth","snid":"%s","cdetab":"%s","typ_srv":"part","phase":"1"}' % (str(uuid4()), 123456, cdetab),
bpcesta = {
'csid': str(uuid4()),
'typ_app': 'rest',
'enseigne': 'bp',
'typ_sp': 'out-band',
'typ_act': 'auth',
'snid': '123456',
'cdetab': cdetab,
'typ_srv': self.user_type,
claims = {
'userinfo': {
'cdetab': None,
'authMethod': None,
'authLevel': None
'id_token': {
'auth_time': {
'essential': True,
'last_login': None,
# We need to avoid to add "phase":"1" for part in bpaca website
# Maybe it will be the case for other websites
# The phase information seems to be in js file and the value is not hardcode
if not in ('', ) or self.user_type != 'part':
# Here if we don't add phase":"1" we would get false wrongpass
bpcesta['phase'] = "1"
'nonce': nonce,
'scope': '',
'response_type': 'id_token token',
'response_mode': 'form_post',
'cdetab': cdetab,
'login_hint': self.username.upper(),
'display': 'page',
'client_id': client_id,
'claims': json.dumps(claims),
'bpcesta': json.dumps(bpcesta),
......@@ -373,7 +432,7 @@ class BanquePopulaire(LoginBrowser):
'token_type': 'Bearer',
'grant_type': 'implicit flow',
'NameId': self.username.upper(),
'Segment': 'part',
'Segment': self.user_type,
'scopes': '',
'expires_in': expires_in,
......@@ -321,6 +321,9 @@ class JsFilePage(AbstractPage):
PARENT_URL = 'js_file'
BROWSER_ATTR = 'package.browser.CaisseEpargne'
def get_user_info_client_id(self):
return Regexp(pattern=r'anonymous:{clientId:"([^"]+)"').filter(self.text)
class AuthorizePage(AbstractPage):
PARENT = 'caissedepargne'
......@@ -337,6 +340,22 @@ class LoginTokensPage(AbstractPage):
return Dict('parameters/expires_in')(self.doc)
class InfoTokensPage(JsonPage):
def get_access_token(self):
return Dict('access_token')(self.doc)
def get_user_type(self):
user_subscription = Dict('characteristics/subscribeTypeItems/0/label')(self.doc)
user_types = {
'abonnement Particulier': 'part',
'abonnement Personne Morale': 'ent',
'abonnement EI (pro)': 'pro',
user_type = user_types.get(user_subscription)
assert user_type, "%s user type is not yet handle" % user_subscription
return user_type
class VkImagePage(AbstractPage):
PARENT = 'caissedepargne'
PARENT_URL = 'vk_image'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment