Skip to content

Commit 40f17c8

Browse files
author
Callum Oakley
committed
moved validate_webhook and authenticate out in to a client of their own
1 parent 4acb987 commit 40f17c8

File tree

5 files changed

+128
-77
lines changed

5 files changed

+128
-77
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ pusher.trigger_batch([
153153

154154
## Push Notifications (BETA)
155155

156-
Pusher now allows sending native notifications to iOS and Android devices. Check out the [documentation](https://pusher.com/docs/push_notifications) for information on how to set up push notifications on Android and iOS. There is no additional setup required to use it with this library. It works out of the box wit the same Pusher instance. All you need are the same pusher credentials.
156+
Pusher now allows sending native notifications to iOS and Android devices. Check out the [documentation](https://pusher.com/docs/push_notifications) for information on how to set up push notifications on Android and iOS. There is no additional setup required to use it with this library. It works out of the box with the same Pusher instance. All you need are the same pusher credentials.
157157

158158
### Sending native pushes
159159

pusher/authentication_client.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import (
4+
print_function,
5+
unicode_literals,
6+
absolute_import,
7+
division)
8+
9+
from pusher.util import (
10+
ensure_text,
11+
validate_channel,
12+
validate_socket_id,
13+
data_to_string)
14+
15+
from pusher.client import Client
16+
from pusher.http import GET, POST, Request, request_method
17+
18+
import collections
19+
import hashlib
20+
import json
21+
import os
22+
import re
23+
import six
24+
import time
25+
26+
27+
class AuthenticationClient(Client):
28+
def __init__(
29+
self, app_id, key, secret, ssl=True, host=None, port=None,
30+
timeout=5, cluster=None, json_encoder=None, json_decoder=None,
31+
backend=None, **backend_options):
32+
super(AuthenticationClient, self).__init__(
33+
app_id, key, secret, ssl, host, port, timeout, cluster,
34+
json_encoder, json_decoder, backend, **backend_options)
35+
36+
if host:
37+
self._host = ensure_text(host, "host")
38+
39+
elif cluster:
40+
self._host = (
41+
six.text_type("api-%s.pusher.com") %
42+
ensure_text(cluster, "cluster"))
43+
else:
44+
self._host = six.text_type("api.pusherapp.com")
45+
46+
47+
def authenticate(self, channel, socket_id, custom_data=None):
48+
"""Used to generate delegated client subscription token.
49+
50+
:param channel: name of the channel to authorize subscription to
51+
:param socket_id: id of the socket that requires authorization
52+
:param custom_data: used on presence channels to provide user info
53+
"""
54+
channel = validate_channel(channel)
55+
56+
if not channel_name_re.match(channel):
57+
raise ValueError('Channel should be a valid channel, got: %s' % channel)
58+
59+
socket_id = validate_socket_id(socket_id)
60+
61+
if custom_data:
62+
custom_data = json.dumps(custom_data, cls=self._json_encoder)
63+
64+
string_to_sign = "%s:%s" % (socket_id, channel)
65+
66+
if custom_data:
67+
string_to_sign += ":%s" % custom_data
68+
69+
signature = sign(self.secret, string_to_sign)
70+
71+
auth = "%s:%s" % (self.key, signature)
72+
result = {'auth': auth}
73+
74+
if custom_data:
75+
result['channel_data'] = custom_data
76+
77+
return result
78+
79+
80+
def validate_webhook(self, key, signature, body):
81+
"""Used to validate incoming webhook messages. When used it guarantees
82+
that the sender is Pusher and not someone else impersonating it.
83+
84+
:param key: key used to sign the body
85+
:param signature: signature that was given with the body
86+
:param body: content that needs to be verified
87+
"""
88+
key = ensure_text(key, "key")
89+
signature = ensure_text(signature, "signature")
90+
body = ensure_text(body, "body")
91+
92+
if key != self.key:
93+
return None
94+
95+
if not verify(self.secret, body, signature):
96+
return None
97+
98+
try:
99+
body_data = json.loads(body, cls=self._json_decoder)
100+
101+
except ValueError:
102+
return None
103+
104+
time_ms = body_data.get('time_ms')
105+
if not time_ms:
106+
return None
107+
108+
if abs(time.time()*1000 - time_ms) > 300000:
109+
return None
110+
111+
return body_data

pusher/pusher.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pusher.signature import sign, verify
1818
from pusher.pusher_client import PusherClient
1919
from pusher.notification_client import NotificationClient
20+
from pusher.authentication_client import AuthenticationClient
2021

2122
import collections
2223
import hashlib
@@ -55,6 +56,10 @@ def __init__(
5556
app_id, key, secret, ssl, host, port, timeout, cluster,
5657
json_encoder, json_decoder, backend, **backend_options)
5758

59+
self._authentication_client = AuthenticationClient(
60+
app_id, key, secret, ssl, host, port, timeout, cluster,
61+
json_encoder, json_decoder, backend, **backend_options)
62+
5863
self._notification_client = NotificationClient(
5964
app_id, key, secret, notification_ssl, notification_host, port,
6065
timeout, cluster, json_encoder, json_decoder, backend,
@@ -63,7 +68,7 @@ def __init__(
6368

6469
@classmethod
6570
def from_url(cls, url, **options):
66-
"""Alternate constructor that extracts the information from a URL.
71+
"""Alternative constructor that extracts the information from a URL.
6772
6873
:param url: String containing a URL
6974
@@ -93,7 +98,7 @@ def from_url(cls, url, **options):
9398

9499
@classmethod
95100
def from_env(cls, env='PUSHER_URL', **options):
96-
"""Alternate constructor that extracts the information from an URL
101+
"""Alternative constructor that extracts the information from an URL
97102
stored in an environment variable. The pusher heroku addon will set
98103
the PUSHER_URL automatically when installed for example.
99104
@@ -121,6 +126,7 @@ def trigger(self, channels, event_name, data, socket_id=None):
121126
def trigger_batch(self, batch=[], already_encoded=False):
122127
return self._pusher_client.trigger_batch(batch, already_encoded)
123128

129+
124130
@doc_string(PusherClient.channels_info.__doc__)
125131
def channels_info(self, prefix_filter=None, attributes=[]):
126132
return self._pusher_client.channels_info(prefix_filter, attributes)
@@ -136,14 +142,16 @@ def users_info(self, channel):
136142
return self._pusher_client.users_info(channel)
137143

138144

139-
@doc_string(PusherClient.authenticate.__doc__)
145+
@doc_string(AuthenticationClient.authenticate.__doc__)
140146
def authenticate(self, channel, socket_id, custom_data=None):
141-
return self.pusher_client.authenticate(channel, socket_id, custom_data)
147+
return self._authentication_client.authenticate(
148+
channel, socket_id, custom_data)
142149

143150

144-
@doc_string(PusherClient.validate_webhook.__doc__)
145-
def authenticate(self, key, signature, body):
146-
return self.pusher_client.validate_webhook(key, signature, body)
151+
@doc_string(AuthenticationClient.validate_webhook.__doc__)
152+
def validate_webhook(self, key, signature, body):
153+
return self._authentication_client.validate_webhook(
154+
key, signature, body)
147155

148156

149157
@doc_string(NotificationClient.notify.__doc__)

pusher/pusher_client.py

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ class PusherClient(Client):
2929
def __init__(
3030
self, app_id, key, secret, ssl=True, host=None, port=None,
3131
timeout=5, cluster=None, json_encoder=None, json_decoder=None,
32-
backend=None, notification_host=None, notification_ssl=True,
33-
**backend_options):
32+
backend=None, **backend_options):
3433
super(PusherClient, self).__init__(
3534
app_id, key, secret, ssl, host, port, timeout, cluster,
3635
json_encoder, json_decoder, backend, **backend_options)
@@ -147,70 +146,3 @@ def users_info(self, channel):
147146

148147
return Request(
149148
self, GET, "/apps/%s/channels/%s/users" % (self.app_id, channel))
150-
151-
152-
def authenticate(self, channel, socket_id, custom_data=None):
153-
"""Used to generate delegated client subscription token.
154-
155-
:param channel: name of the channel to authorize subscription to
156-
:param socket_id: id of the socket that requires authorization
157-
:param custom_data: used on presence channels to provide user info
158-
"""
159-
channel = validate_channel(channel)
160-
161-
if not channel_name_re.match(channel):
162-
raise ValueError('Channel should be a valid channel, got: %s' % channel)
163-
164-
socket_id = validate_socket_id(socket_id)
165-
166-
if custom_data:
167-
custom_data = json.dumps(custom_data, cls=self._json_encoder)
168-
169-
string_to_sign = "%s:%s" % (socket_id, channel)
170-
171-
if custom_data:
172-
string_to_sign += ":%s" % custom_data
173-
174-
signature = sign(self.secret, string_to_sign)
175-
176-
auth = "%s:%s" % (self.key, signature)
177-
result = {'auth': auth}
178-
179-
if custom_data:
180-
result['channel_data'] = custom_data
181-
182-
return result
183-
184-
185-
def validate_webhook(self, key, signature, body):
186-
"""Used to validate incoming webhook messages. When used it guarantees
187-
that the sender is Pusher and not someone else impersonating it.
188-
189-
:param key: key used to sign the body
190-
:param signature: signature that was given with the body
191-
:param body: content that needs to be verified
192-
"""
193-
key = ensure_text(key, "key")
194-
signature = ensure_text(signature, "signature")
195-
body = ensure_text(body, "body")
196-
197-
if key != self.key:
198-
return None
199-
200-
if not verify(self.secret, body, signature):
201-
return None
202-
203-
try:
204-
body_data = json.loads(body, cls=self._json_decoder)
205-
206-
except ValueError:
207-
return None
208-
209-
time_ms = body_data.get('time_ms')
210-
if not time_ms:
211-
return None
212-
213-
if abs(time.time()*1000 - time_ms) > 300000:
214-
return None
215-
216-
return body_data

0 commit comments

Comments
 (0)