Skip to content

Commit d92d1ff

Browse files
committed
Add authenticate_user method
1 parent ea0b306 commit d92d1ff

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

pusher_push_notifications/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
"""Pusher Push Notifications Python server SDK"""
22

3+
import datetime
34
import json
45
import re
6+
import time
57

8+
import jwt
69
import requests
710
import six
811

912
SDK_VERSION = '1.0.3'
13+
1014
INTEREST_MAX_LENGTH = 164
1115
INTEREST_REGEX = re.compile('^(_|-|=|@|,|\\.|;|[A-Z]|[a-z]|[0-9])*$')
1216
MAX_NUMBER_OF_INTERESTS = 100
1317

18+
USER_ID_MAX_LENGTH = 164
19+
AUTH_TOKEN_DURATION = datetime.timedelta(days=1)
20+
1421

1522
class PusherError(Exception):
1623
"""Base class for all Pusher push notifications errors"""
@@ -174,3 +181,39 @@ def publish(self, interests, publish_body):
174181
handle_http_error(response_body, response.status_code)
175182

176183
return response_body
184+
185+
def authenticate_user(self, user_id):
186+
"""Generate an auth token which will allow devices to associate
187+
themselves with the given user id
188+
189+
Args:
190+
user_id (string): user id for which the token will be valid
191+
192+
Returns:
193+
auth token for the requested user id (string)
194+
195+
Raises:
196+
ValueError: if user_id is not a string
197+
ValueError: is user_id is longer than the maximum of 164 chars
198+
199+
"""
200+
if not isinstance(user_id, six.string_types):
201+
raise ValueError('user_id must be a string')
202+
if len(user_id) > USER_ID_MAX_LENGTH:
203+
raise ValueError('user_id longer than the maximum of 164 chars')
204+
205+
issuer = 'https://{}.pushnotifications.pusher.com'.format(self.instance_id)
206+
207+
now = datetime.datetime.utcnow()
208+
expiry_datetime = now + AUTH_TOKEN_DURATION
209+
expiry_timestamp = int(time.mktime(expiry_datetime.timetuple()))
210+
211+
return jwt.encode(
212+
{
213+
'iss': issuer,
214+
'sub': user_id,
215+
'exp': expiry_timestamp,
216+
},
217+
self.secret_key,
218+
algorithm='HS256',
219+
).decode('utf-8')

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
six>1.4.0,<2
22
requests>2.5.0,<3
3+
pyjwt==1.7.1

tests/test_push_notifications.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Unit tests for Pusher Push Notifications Python server SDK"""
22

3+
import six
4+
import time
35
import unittest
46

7+
import jwt
58
import requests_mock
69

7-
810
from pusher_push_notifications import (
911
PushNotifications,
1012
PusherAuthError,
@@ -456,3 +458,49 @@ def test_publish_should_error_correctly_if_error_not_json(self):
456458
)
457459
self.assertIn('Unknown error: no description', str(e.exception))
458460

461+
def test_authenticate_user_should_return_token(self):
462+
user_id = 'user-0001'
463+
pn_client = PushNotifications(
464+
'INSTANCE_ID',
465+
'SECRET_KEY'
466+
)
467+
468+
token_string = pn_client.authenticate_user(user_id)
469+
470+
self.assertIsInstance(token_string, six.string_types)
471+
self.assertTrue(len(token_string) > 0)
472+
473+
decoded_token = jwt.decode(
474+
token_string,
475+
'SECRET_KEY',
476+
algorithm='HS256',
477+
)
478+
479+
expected_issuer = 'https://INSTANCE_ID.pushnotifications.pusher.com'
480+
expected_subject = user_id
481+
482+
self.assertEquals(decoded_token.get('iss'), expected_issuer)
483+
self.assertEquals(decoded_token.get('sub'), expected_subject)
484+
self.assertIsNotNone(decoded_token.get('exp'))
485+
self.assertTrue(decoded_token.get('exp') > time.time())
486+
487+
488+
def test_authenticate_user_should_fail_if_user_id_not_a_string(self):
489+
user_id = False
490+
pn_client = PushNotifications(
491+
'INSTANCE_ID',
492+
'SECRET_KEY'
493+
)
494+
with self.assertRaises(ValueError) as e:
495+
pn_client.authenticate_user(user_id)
496+
self.assertIn('user_id must be a string', str(e.exception))
497+
498+
def test_authenticate_user_should_fail_if_user_id_too_long(self):
499+
user_id = 'A' * 165
500+
pn_client = PushNotifications(
501+
'INSTANCE_ID',
502+
'SECRET_KEY'
503+
)
504+
with self.assertRaises(ValueError) as e:
505+
pn_client.authenticate_user(user_id)
506+
self.assertIn('longer than the maximum of 164 chars', str(e.exception))

0 commit comments

Comments
 (0)