Skip to content

Commit 3b8cb72

Browse files
committed
Refactor how requests are made
- Create a single abstraction for making requests - Escape path paramaters for security reasons
1 parent 0ffa5ee commit 3b8cb72

File tree

2 files changed

+75
-79
lines changed

2 files changed

+75
-79
lines changed

pusher_push_notifications/__init__.py

Lines changed: 73 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import re
77
import time
8+
import urllib
89
import warnings
910

1011
import jwt
@@ -49,8 +50,7 @@ class PusherBadResponseError(PusherError, Exception):
4950
"""
5051

5152

52-
def handle_http_error(response_body, status_code):
53-
"""Handle different http error codes from the Push Notifications service"""
53+
def _handle_http_error(response_body, status_code):
5454
error_string = '{}: {}'.format(
5555
response_body.get('error', 'Unknown error'),
5656
response_body.get('description', 'no description'),
@@ -65,6 +65,17 @@ def handle_http_error(response_body, status_code):
6565
raise PusherServerError(error_string)
6666

6767

68+
def _make_url(scheme, host, path):
69+
return urllib.parse.urlunparse([
70+
scheme,
71+
host,
72+
path,
73+
None,
74+
None,
75+
None,
76+
])
77+
78+
6879
class PushNotifications(object):
6980
"""Pusher Push Notifications API client
7081
This client class can be used to publish notifications to the Pusher
@@ -97,6 +108,46 @@ def endpoint(self):
97108
).lower()
98109
return self._endpoint or default_endpoint
99110

111+
def _make_request(self, method, path, path_params, body=None):
112+
path_params = {
113+
name: urllib.parse.quote(value)
114+
for name, value in path_params.items()
115+
}
116+
path = path.format(**path_params)
117+
url = _make_url(scheme='https', host=self.endpoint, path=path)
118+
119+
session = requests.Session()
120+
request = requests.Request(
121+
method,
122+
url,
123+
json=body,
124+
headers={
125+
'host': self.endpoint,
126+
'authorization': 'Bearer {}'.format(self.secret_key),
127+
'x-pusher-library': 'pusher-push-notifications-python {}'.format(
128+
SDK_VERSION,
129+
)
130+
},
131+
)
132+
133+
response = session.send(request.prepare())
134+
135+
if response.status_code != 200:
136+
try:
137+
error_body = response.json()
138+
except ValueError:
139+
error_body = {}
140+
_handle_http_error(error_body, response.status_code)
141+
142+
try:
143+
response_body = response.json()
144+
except ValueError:
145+
raise PusherBadResponseError(
146+
'The server returned a malformed response',
147+
)
148+
149+
return response_body
150+
100151
def publish(self, interests, publish_body):
101152
"""Publish the given publish_body to the specified interests.
102153
@@ -201,39 +252,16 @@ def publish_to_interests(self, interests, publish_body):
201252

202253
publish_body = copy.deepcopy(publish_body)
203254
publish_body['interests'] = interests
204-
session = requests.Session()
205-
request = requests.Request(
206-
'POST',
207-
'https://{}/publish_api/v1/instances/{}/publishes'.format(
208-
self.endpoint,
209-
self.instance_id,
210-
),
211-
json=publish_body,
212-
headers={
213-
'host': self.endpoint,
214-
'authorization': 'Bearer {}'.format(self.secret_key),
215-
'x-pusher-library': 'pusher-push-notifications-python {}'.format(
216-
SDK_VERSION,
217-
)
255+
256+
response_body = self._make_request(
257+
method='POST',
258+
path='/publish_api/v1/instances/{instance_id}/publishes/interests',
259+
path_params={
260+
'instance_id': self.instance_id,
218261
},
262+
body=publish_body,
219263
)
220264

221-
response = session.send(request.prepare())
222-
223-
if response.status_code != 200:
224-
try:
225-
response_body = response.json()
226-
except ValueError:
227-
response_body = {}
228-
handle_http_error(response_body, response.status_code)
229-
230-
try:
231-
response_body = response.json()
232-
except ValueError:
233-
raise PusherBadResponseError(
234-
'The server returned a malformed response',
235-
)
236-
237265
return response_body
238266

239267
def publish_to_users(self, user_ids, publish_body):
@@ -293,39 +321,16 @@ def publish_to_users(self, user_ids, publish_body):
293321

294322
publish_body = copy.deepcopy(publish_body)
295323
publish_body['users'] = user_ids
296-
session = requests.Session()
297-
request = requests.Request(
298-
'POST',
299-
'https://{}/publish_api/v1/instances/{}/publishes/users'.format(
300-
self.endpoint,
301-
self.instance_id,
302-
),
303-
json=publish_body,
304-
headers={
305-
'host': self.endpoint,
306-
'authorization': 'Bearer {}'.format(self.secret_key),
307-
'x-pusher-library': 'pusher-push-notifications-python {}'.format(
308-
SDK_VERSION,
309-
)
324+
325+
response_body = self._make_request(
326+
method='POST',
327+
path='/publish_api/v1/instances/{instance_id}/publishes/users',
328+
path_params={
329+
'instance_id': self.instance_id,
310330
},
331+
body=publish_body,
311332
)
312333

313-
response = session.send(request.prepare())
314-
315-
if response.status_code != 200:
316-
try:
317-
response_body = response.json()
318-
except ValueError:
319-
response_body = {}
320-
handle_http_error(response_body, response.status_code)
321-
322-
try:
323-
response_body = response.json()
324-
except ValueError:
325-
raise PusherBadResponseError(
326-
'The server returned a malformed response',
327-
)
328-
329334
return response_body
330335

331336
def authenticate_user(self, user_id):
@@ -385,20 +390,11 @@ def delete_user(self, user_id):
385390
if len(user_id) > USER_ID_MAX_LENGTH:
386391
raise ValueError('user_id longer than the maximum of 164 chars')
387392

388-
session = requests.Session()
389-
request = requests.Request(
390-
'DELETE',
391-
'https://{}/user_api/v1/instances/{}/users/{}'.format(
392-
self.endpoint,
393-
self.instance_id,
394-
user_id,
395-
),
396-
headers={
397-
'host': self.endpoint,
398-
'authorization': 'Bearer {}'.format(self.secret_key),
399-
'x-pusher-library': 'pusher-push-notifications-python {}'.format(
400-
SDK_VERSION,
401-
)
393+
self._make_request(
394+
method='DELETE',
395+
path='/user_api/v1/instances/{instance_id}/users/{user_id}',
396+
path_params={
397+
'instance_id': self.instance_id,
398+
'user_id': user_id,
402399
},
403400
)
404-
session.send(request.prepare())

tests/test_interests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def test_publish_to_interests_should_make_correct_http_request(self):
5353
)
5454
self.assertEqual(
5555
path,
56-
'/publish_api/v1/instances/instance_id/publishes',
56+
'/publish_api/v1/instances/instance_id/publishes/interests',
5757
)
5858
self.assertDictEqual(
5959
headers,
@@ -120,7 +120,7 @@ def test_deprecated_alias_still_works(self):
120120
)
121121
self.assertEqual(
122122
path,
123-
'/publish_api/v1/instances/instance_id/publishes',
123+
'/publish_api/v1/instances/instance_id/publishes/interests',
124124
)
125125
self.assertDictEqual(
126126
headers,

0 commit comments

Comments
 (0)