Skip to content
Commits on Source (3)
......@@ -64,8 +64,8 @@ class Paypal(LoginBrowser):
HistoryDetailsPage)
promo = URL('https://www.paypal.com/fr/webapps/mpp/clickthru/paypal-app-promo-2.*',
'/fr/webapps/mpp/clickthru.*', PromoPage)
account = URL('https://www.paypal.com/businessexp/money',
'https://www.paypal.com/myaccount/money',
account = URL('https://www.paypal.com/myaccount/money',
'https://www.paypal.com/businessexp/money',
'https://www.paypal.com/webapps/business/money', AccountPage)
pro_history = URL('https://\w+.paypal.com/businessexp/transactions/activity\?.*',
ProHistoryPage)
......@@ -97,15 +97,15 @@ def do_login(self):
data['_sessionID'] = sessionID
data[key] = value
res = self.open('/auth/verifychallenge', data=data)
if not 'OK' in res.content:
if not 'OK' in res.text:
raise BrowserUnavailable('Challenge failed')
res = self.page.login(self.username, self.password)
if 'LoginFailed' in res.content or 'Sorry, we can\'t log you in' in res.content or self.error.is_here():
if 'LoginFailed' in res.text or 'Sorry, we can\'t log you in' in res.text or self.error.is_here():
raise BrowserIncorrectPassword()
if '/auth/validatecaptcha' in res.content:
if '/auth/validatecaptcha' in res.text:
raise BrowserUnavailable('captcha')
self.location('/')
......
......@@ -21,6 +21,7 @@
from decimal import Decimal, ROUND_DOWN
import json
import re
import urllib.parse
from weboob.tools.compat import unicode
from weboob.capabilities.bank import Account
......@@ -54,78 +55,59 @@ def on_load(self):
class LoginPage(HTMLPage):
def get_token_and_csrf(self, code):
mtc = re.search(r'var (_0x\w{4})=function.*?\};', code)
decoder_name = mtc.group(1)
decoder_code = re.search(r'var _0x\w{4}=\[.*var (_0x\w{4})=function.*?\};', code).group(0)
decoder_code += """
;function mapDecoder(array) {
var map = {};
for (var key in array) {
map[key] = %s(key);
}
return map;
}
""" % decoder_name
decoder_js = Javascript(decoder_code)
# clean string obfuscation like: '\x70\x61\x79\x70\x61\x6c\x20\x73\x75\x63\x6b\x73'
def basic_decoder(mtc):
return repr(literal_eval(mtc.group(0)).encode('utf-8'))
cleaner_code = re.sub(r"'.*?(?<!\\)'", basic_decoder, code)
# clean other obfuscation like: _0x1234('0x42')
# Do only one call to JS by putting all the elements to decode in an
# array that will be mapped in JS to a structure of the form {encoded:
# decoded}.
to_decode = set()
for m in re.finditer(r"%s\('([^']+)'\)" % re.escape(decoder_name), cleaner_code):
to_decode.add(literal_eval(m.group(1)))
decoded_map = decoder_js.call('mapDecoder', [k for k in to_decode])
def exec_decoder(mtc):
key = repr(literal_eval(mtc.group(1)))
# Use json.dumps to force utf8 encoding.
ret = json.dumps(str(decoded_map[key]))
# Force simple quoting around the string.
return "'" + ret[1:-1] + "'"
cleaner_code = re.sub(r"%s\('([^']+)'\)" % re.escape(decoder_name), exec_decoder, cleaner_code)
cookie = re.search(r'xppcts = (\w+);', cleaner_code).group(1)
sessionID = re.search(r"%s\w+\('([^']+)'" % re.escape("'&_sessionID='+encodeURIComponent("), cleaner_code).group(1)
csrf = re.search(r"%s'([^']+)'" % re.escape("'&_csrf='+encodeURIComponent("), cleaner_code).group(1)
key, value = re.findall(r"'(\w+)','(\w+)'", cleaner_code)[-1]
# Remove setCookie function content
cleaner_code = re.sub(r"'setCookie'.*(?=,'removeCookie')", "'setCookie':function(){}", cleaner_code)
# Detect the name of the function that computes the token, detect the
# variable that stores the result and store it as a global.
get_token_func_name = re.search(r"ads_token_js='\+encodeURIComponent\((\w+)\)", cleaner_code).group(1)
get_token_func_declaration = "var " + get_token_func_name + "="
cleaner_code = cleaner_code.replace(get_token_func_declaration, get_token_func_declaration + "window.ADS_JS_TOKEN=")
# Paypal will try to create an infinite loop to make the parse fail, based on different
# weird things like a check of 'ind\\u0435xOf' vs 'indexOf'.
cleaner_code = cleaner_code.replace(r"'ind\\u0435xOf'", "'indexOf'")
cleaner_code = code.replace(r"'ind\\u0435xOf'", "'indexOf'")
# It also calls "data" which is undefined instead of a return (next call is an infinite
# recursive function). This should theorically not happen if window.domain is correctly set
# to "paypal.com" though.
cleaner_code = cleaner_code.replace("data;", "return;")
# Add a function that returns the token
cleaner_code += """
function GET_ADS_JS_TOKEN()
# Remove setCookie function content
cleaner_code = re.sub(r"'setCookie'.*(?=,'removeCookie')", "'setCookie':function(){}", cleaner_code)
# Paypal will try to send a XHR, let's use a fake method to catch the values sent
cleaner_code = """
XMLHttpRequest.prototype.send = function(body)
{
return window.ADS_JS_TOKEN || "INVALID_TOKEN";
window.PAYPAL_TOKENS = body;
};
function GET_JS_TOKENS()
{
return window.PAYPAL_TOKENS || "INVALID_TOKENS";
}
"""
""" + cleaner_code
try:
token = str(Javascript(cleaner_code, None, "paypal.com").call("GET_ADS_JS_TOKEN"))
raw = str(Javascript(cleaner_code, None, "paypal.com").call("GET_JS_TOKENS"))
raw = raw.split("&")
tokens = {}
for r in raw:
r = r.split("=")
k = r[0]
v = urllib.parse.unquote(r[1])
if k not in ["ads_token_js", "_sessionID", "_csrf"]:
tokens["key"] = k
tokens["value"] = v
else:
tokens[k] = v
token = tokens["ads_token_js"]
sessionID = tokens["_sessionID"]
csrf = tokens["_csrf"]
key = tokens["key"]
value = tokens["value"]
except:
raise BrowserUnavailable()
raise BrowserUnavailable("Could not grab tokens")
# Clean string obfuscation like: '\x70\x61\x79\x70\x61\x6c\x20\x73\x75\x63\x6b\x73'
def basic_decoder(mtc):
return repr(literal_eval(mtc.group(0)).encode('utf-8'))
cleaner_code = re.sub(r"'.*?(?<!\\)'", basic_decoder, code)
cookie = re.search(r'xppcts = (\w+);', cleaner_code).group(1)
return token, csrf, key, value, sessionID, cookie
......@@ -288,7 +270,7 @@ def parse_transaction(self, transaction, account):
return []
cc = [tr['grossAmount']['amountUnformatted'] for tr in transaction['secondaryTransactions'] \
if account.currency == tr['grossAmount']['currency'] \
and (tr['grossAmount']['amountUnformatted'] < 0) == (transaction['grossAmount']['amountUnformatted'] < 0) \
and (int(tr['grossAmount']['amountUnformatted']) < 0) == (int(transaction['grossAmount']['amountUnformatted']) < 0) \
and tr['transactionDescription']['description'].startswith('Conversion de devise')]
if not cc:
return []
......