Skip to content
This repository was archived by the owner on Jan 25, 2018. It is now read-only.

Commit bbdbe89

Browse files
author
Andy McKay
committed
make sure personal lookups and headers actually work (bug 774852)
1 parent 0c6895b commit bbdbe89

File tree

8 files changed

+85
-26
lines changed

8 files changed

+85
-26
lines changed

lib/paypal/client.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def receivers(self, seller_email, amount, preapproval, chains=None):
102102

103103
return result
104104

105-
def call(self, service, data):
105+
def call(self, service, data, auth_token=None):
106106
"""
107107
Wrapper around calling the requested paypal service using
108108
data provided. Adds in timing and logging.
@@ -111,7 +111,7 @@ def call(self, service, data):
111111
url = urls[service]
112112
with statsd.timer('solitude.paypal.%s' % service):
113113
log.info('Calling service: %s' % service)
114-
return self._call(url, data)
114+
return self._call(url, data, auth_token=auth_token)
115115

116116
def headers(self, url, auth_token=None):
117117
"""
@@ -127,7 +127,6 @@ def headers(self, url, auth_token=None):
127127
if auth_token:
128128
ts, sig = get_auth_header(auth['USER'], auth['PASSWORD'],
129129
auth_token['token'], auth_token['secret'], 'POST', url)
130-
131130
headers['X-PAYPAL-AUTHORIZATION'] = (
132131
'timestamp=%s,token=%s,signature=%s' %
133132
(ts, auth_token['token'], sig))
@@ -202,13 +201,14 @@ def check_permission(self, token, permissions):
202201
result = [v for (k, v) in res.iteritems() if k.startswith('scope')]
203202
return {'status': set(permissions).issubset(set(result))}
204203

205-
def get_permission_token(self, token, code):
204+
def get_permission_token(self, token, verifier):
206205
"""
207206
Send request for permissions token, after user has granted the
208207
requested permissions via the PayPal page we redirected them to.
209208
Documentation: http://bit.ly/Mjh51D
210209
"""
211-
res = self.call('get-permission-token', {'token': token, 'code': code})
210+
res = self.call('get-permission-token', {'token': token,
211+
'verifier': verifier})
212212
return {'token': res['token'], 'secret': res['tokenSecret']}
213213

214214
def get_preapproval_key(self, start, end, return_url, cancel_url):
@@ -289,7 +289,8 @@ def get_personal_basic(self, token):
289289
keys = ['first_name', 'last_name', 'email', 'full_name',
290290
'company', 'country', 'payerID']
291291
data = {'attributeList.attribute': [PAYPAL_PERSONAL[k] for k in keys]}
292-
return self.parse_personal(self.call('get-personal', data))
292+
return self.parse_personal(self.call('get-personal', data,
293+
auth_token=token))
293294

294295
def get_personal_advanced(self, token):
295296
"""
@@ -299,7 +300,8 @@ def get_personal_advanced(self, token):
299300
keys = ['post_code', 'address_one', 'address_two', 'city', 'state',
300301
'phone']
301302
data = {'attributeList.attribute': [PAYPAL_PERSONAL[k] for k in keys]}
302-
return self.parse_personal(self.call('get-personal', data))
303+
return self.parse_personal(self.call('get-personal-advanced', data,
304+
auth_token=token))
303305

304306
def parse_refund(self, res):
305307
responses = defaultdict(lambda: defaultdict(dict))

lib/paypal/forms.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ class CheckPermission(ArgForm):
109109

110110
class GetPermissionToken(ArgForm):
111111
token = forms.CharField()
112-
code = forms.CharField()
112+
verifier = forms.CharField()
113113
seller = forms.ModelChoiceField(queryset=SellerPaypal.objects.all(),
114114
to_field_name='seller__uuid')
115115

116-
_args = ('token', 'code')
116+
_args = ('token', 'verifier')
117117

118118

119119
class KeyValidation(forms.Form):
@@ -142,7 +142,16 @@ class GetPersonal(ArgForm):
142142
seller = forms.ModelChoiceField(queryset=SellerPaypal.objects.all(),
143143
to_field_name='seller__uuid')
144144

145-
_args = ('seller',)
145+
_args = ('token',)
146+
147+
def clean(self):
148+
seller = self.cleaned_data.get('seller')
149+
if (not seller.token or not seller.secret):
150+
raise forms.ValidationError('Empty permissions token.')
151+
152+
self.cleaned_data['token'] = {'token': seller.token,
153+
'secret': seller.secret}
154+
return self.cleaned_data
146155

147156

148157
class IPNForm(forms.Form):

lib/paypal/header.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
import base64
22
import hmac
33
import hashlib
4+
import re
45
import time
5-
import urllib
6+
7+
x = re.compile(r'([A-Za-z0-9_]+)')
8+
9+
10+
def escape(value):
11+
# PayPal bizarre escaping.
12+
res = []
13+
for c in str(value):
14+
if not x.match(c):
15+
res.append('%' + hex(ord(c))[2:])
16+
else:
17+
res.append(c)
18+
return ''.join(res)
619

720

821
# TODO: replace this with a damn OAuth library.
922
def get_auth_header(api_user, api_pass, access_tok, sec_tok,
1023
http_method, script_uri):
11-
timestamp = time.time()
12-
data = {
13-
'api_pass': sec_tok,
14-
'http_method': http_method,
15-
'oauth_consumer_key': api_user,
16-
'oauth_signature_method': 'HMAC-SHA1',
17-
'oauth_timestamp': timestamp,
18-
'oauth_token': access_tok,
19-
'oauth_version': 1.0,
20-
}
21-
data = urllib.urlencode(data)
22-
hashed = hmac.new(api_pass, data, hashlib.sha1)
24+
timestamp = int(time.time())
25+
data = (
26+
('oauth_consumer_key', api_user),
27+
('oauth_signature_method', 'HMAC-SHA1'),
28+
('oauth_timestamp', timestamp),
29+
('oauth_token', access_tok),
30+
('oauth_version', 1.0),
31+
)
32+
data = ['%s=%s' % (k, v) for k, v in data]
33+
data = '&'.join((http_method, escape(script_uri), escape('&'.join(data))))
34+
key = '&'.join((api_pass, escape(sec_tok)))
35+
hashed = hmac.new(key, data, hashlib.sha1)
2336
return str(timestamp), base64.b64encode(hashed.digest())

lib/paypal/resources/personal.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def obj_create(self, bundle, request, **kwargs):
1616
for k, v in result.items():
1717
setattr(form.cleaned_data['seller'], k, v)
1818
form.cleaned_data['seller'].save()
19+
bundle.data = result
1920
bundle.obj = form.cleaned_data['seller']
2021
return bundle
2122

lib/paypal/tests/test_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ def setUp(self):
359359
def test_personal_works(self, _call):
360360
_call.return_value = good_personal_basic
361361
eq_(self.paypal.get_personal_basic('foo')['email'], 'batman@gmail.com')
362+
eq_(_call.call_args[1]['auth_token'], 'foo')
362363

363364
def test_personal_absent(self, _call):
364365
_call.return_value = good_personal_basic

lib/paypal/tests/test_forms.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import test_utils
33

44
from lib.buyers.models import Buyer, BuyerPaypal
5-
from lib.paypal.forms import PayValidation
5+
from lib.paypal.forms import GetPersonal, PayValidation
66
from lib.sellers.models import Seller, SellerPaypal
77
from lib.transactions.models import PaypalTransaction
88

@@ -90,3 +90,28 @@ def test_duplicate_uuid(self):
9090
data['uuid'] = 'sample:uuid'
9191
form = PayValidation(data)
9292
assert not form.is_valid(), form.errors
93+
94+
95+
class TestKeyValidation(test_utils.TestCase):
96+
97+
def setUp(self):
98+
self.uuid = 'sample:uid'
99+
self.seller = Seller.objects.create(uuid=self.uuid)
100+
self.paypal = SellerPaypal.objects.create(seller=self.seller,
101+
paypal_id='foo@bar.com')
102+
103+
def test_empty_token(self):
104+
form = GetPersonal({'seller': self.uuid})
105+
assert not form.is_valid()
106+
107+
def test_no_seller(self):
108+
form = GetPersonal()
109+
assert not form.is_valid()
110+
111+
def test_token(self):
112+
self.paypal.token = 'token'
113+
self.paypal.secret = 'secret'
114+
self.paypal.save()
115+
116+
form = GetPersonal({'seller': self.uuid})
117+
assert form.is_valid()

lib/paypal/tests/test_permission.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def setUp(self):
6262
self.list_url = self.get_list_url('permission-token')
6363

6464
def get_data(self):
65-
return {'token': 'foo', 'code': 'bar', 'seller': 'sample:uuid'}
65+
return {'token': 'foo', 'verifier': 'bar', 'seller': 'sample:uuid'}
6666

6767
def test_check_no_seller(self, key):
6868
data = self.get_data()

lib/paypal/tests/test_personal.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from mock import patch
44
from nose.tools import eq_
55

6+
from lib.paypal.header import escape
67
from lib.sellers.models import Seller, SellerPaypal
78
from solitude.base import APITest
89

910

10-
class TestGetBasic(APITest):
11+
class TestGet(APITest):
1112

1213
def setUp(self):
1314
self.api_name = 'paypal'
@@ -21,6 +22,7 @@ def test_basic_data(self, result):
2122
res = self.client.post(self.get_list_url('personal-basic'),
2223
data={'seller': self.uid})
2324
eq_(res.status_code, 201)
25+
eq_(json.loads(res.content)['first_name'], '..')
2426
obj = SellerPaypal.objects.get(pk=self.paypal.pk)
2527
eq_(obj.first_name, '..')
2628

@@ -30,5 +32,11 @@ def test_advanced_data(self, result):
3032
res = self.client.post(self.get_list_url('personal-advanced'),
3133
data={'seller': self.uid})
3234
eq_(res.status_code, 201)
35+
eq_(json.loads(res.content)['phone'], '..')
3336
obj = SellerPaypal.objects.get(pk=self.paypal.pk)
3437
eq_(obj.phone, '..')
38+
39+
40+
def test_header():
41+
eq_(escape('foo'), 'foo')
42+
eq_(escape('&'), '%26')

0 commit comments

Comments
 (0)