def __init__(self, app_id, key, secret, ssl=True, host=None, port=None, timeout=5, cluster=None, encryption_master_key=None, json_encoder=None, json_decoder=None, backend=None, notification_host=None, notification_ssl=True, **backend_options): self._pusher_client = PusherClient(app_id, key, secret, ssl, host, port, timeout, cluster, encryption_master_key, json_encoder, json_decoder, backend, **backend_options) self._authentication_client = AuthenticationClient( app_id, key, secret, ssl, host, port, timeout, cluster, encryption_master_key, json_encoder, json_decoder, backend, **backend_options) self._notification_client = NotificationClient( app_id, key, secret, notification_ssl, notification_host, port, timeout, cluster, json_encoder, json_decoder, backend, **backend_options)
def test_trigger_with_private_encrypted_channel_string_fail_case_no_encryption_master_key_specified( self): pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True) with self.assertRaises(ValueError): pc.trigger(u'private-encrypted-tst', u'some_event', {u'message': u'hello worlds'})
def test_host_behaviour(self): conf = PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True) self.assertEqual(conf.host, u'api.pusherapp.com', u'default host should be correct') conf = PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=u'eu') self.assertEqual(conf.host, u'api-eu.pusher.com', u'host should be overriden by cluster setting') conf = PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, host=u'foo') self.assertEqual(conf.host, u'foo', u'host should be overriden by host setting') conf = PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=u'eu', host=u'plah') self.assertEqual(conf.host, u'plah', u'host should be used in preference to cluster')
def test_host_should_be_text(self): PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, host=u'foo') self.assertRaises( TypeError, lambda: PusherClient( app_id=u'4', key=u'key', secret=u'secret', ssl=True, host=4))
def test_cluster_should_be_text(self): PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=u'eu') self.assertRaises( TypeError, lambda: PusherClient( app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=4) )
def test_trigger_disallow_private_encrypted_channel_with_multiple_channels(self): # instantiate a new client configured with the master encryption key encryp_master_key=u'8tW5FQLniQ1sBQFwrw7t6TVEsJZd10yY' pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key=encryp_master_key, ssl=True) self.assertRaises(ValueError, lambda: self.pusher_client.trigger.make_request([u'my-chan', u'private-encrypted-pippo'], u'some_event', {u'message': u'hello world'}))
def test_authenticate_types(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') self.assertRaises(TypeError, lambda: pusher.authenticate(2423, u'34554')) self.assertRaises(TypeError, lambda: pusher.authenticate(u'plah', 234234)) self.assertRaises(ValueError, lambda: pusher.authenticate(u'::', u'345345'))
def test_trigger_batch_with_mixed_channels_success_case(self): json_dumped = u'{"message": "something"}' encryp_master_key=u'8tW5FQLniQ1sBQFwrw7t6TVEsJZd10yY' event_name_2 = "my-event-2" chan_2 = "private-encrypted-2" payload = {"message": "hello worlds"} pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key=encryp_master_key, ssl=True) request = pc.trigger_batch.make_request( [{ u'channel': u'my-chan', u'name': u'my-event', u'data': {u'message': u'something'} },{ u'channel': chan_2, u'name': event_name_2, u'data': payload }] ) # simulate the same encryption process and check equality encryp_master_key = ensure_binary(encryp_master_key,"encryp_master_key") chan_2 = ensure_binary(chan_2,"chan_2") shared_secret = generate_shared_secret(chan_2, encryp_master_key) box = nacl.secret.SecretBox(shared_secret) nonce_b64 = json.loads(request.params["batch"][1]["data"])["nonce"].encode("utf-8") nonce = base64.b64decode(nonce_b64) encrypted = box.encrypt(json.dumps(payload, ensure_ascii=False).encode("utf'-8"), nonce) # obtain the ciphertext cipher_text = encrypted.ciphertext # encode cipertext to base64 cipher_text_b64 = base64.b64encode(cipher_text) # format expected output json_dumped_2 = json.dumps({ "nonce" : nonce_b64.decode("utf-8"), "ciphertext": cipher_text_b64.decode("utf-8") } , ensure_ascii=False) expected_params = { u'batch': [{ u'channel': u'my-chan', u'name': u'my-event', u'data': json_dumped }, { u'channel': u'private-encrypted-2', u'name': event_name_2, u'data': json_dumped_2 }] } self.assertEqual(request.params, expected_params)
def test_validate_webhook_bad_signature(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') body = u'some body' signature = u'some signature' with mock.patch('time.time') as time_mock: self.assertEqual( pusher.validate_webhook(pusher.key, signature, body), None) time_mock.assert_not_called()
def test_validate_webhook_bad_time(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') body = u'{"time_ms": 1000000}' signature = six.text_type( hmac.new(pusher.secret.encode('utf8'), body.encode('utf8'), hashlib.sha256).hexdigest()) with mock.patch('time.time', return_value=1301): self.assertEqual( pusher.validate_webhook(pusher.key, signature, body), None)
def test_trigger_disallow_private_encrypted_channel_with_multiple_channels( self): pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key_base64= u'OHRXNUZRTG5pUTFzQlFGd3J3N3Q2VFZFc0paZDEweVk=', ssl=True) self.assertRaises( ValueError, lambda: self.pusher_client.trigger. make_request([u'my-chan', u'private-encrypted-pippo'], u'some_event', {u'message': u'hello world'}))
def test_validate_webhook_bad_key(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') body = u'some body' signature = six.text_type( hmac.new(pusher.secret.encode(u'utf8'), body.encode(u'utf8'), hashlib.sha256).hexdigest()) with mock.patch('time.time') as time_mock: self.assertEqual( pusher.validate_webhook(u'badkey', signature, body), None) time_mock.assert_not_called()
def test_trigger_success_when_len_event_name_not_greater_than_max_specified(self): json_dumped = u'{"message": "hello worlds"}' pusher_client = PusherClient(app_id=u'4', key=u'key', secret=u'secret', host=u'somehost', max_len_event_name = 5000) with mock.patch('json.dumps', return_value=json_dumped) as json_dumps_mock: request = pusher_client.trigger.make_request(u'some_channel', u'some_event'*100, {u'message': u'hello worlds'}) expected_params = { u'channels': [u'some_channel'], u'data': json_dumped, u'name': u'some_event'*100 } self.assertEqual(request.params, expected_params)
def test_trigger_with_public_channel_with_encryption_master_key_specified_success(self): json_dumped = u'{"message": "something"}' pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key=u'8tW5FQLniQ1sBQFwrw7t6TVEsJZd10yY', ssl=True) with mock.patch('json.dumps', return_value=json_dumped) as json_dumps_mock: request = pc.trigger.make_request(u'donuts', u'some_event', {u'message': u'hello worlds'}) expected_params = { u'channels': [u'donuts'], u'data': json_dumped, u'name': u'some_event' } self.assertEqual(request.params, expected_params)
def __init__( self, app_id, key, secret, ssl=True, host=None, port=None, timeout=5, cluster=None, encryption_master_key=None, json_encoder=None, json_decoder=None, backend=None, notification_host=None, notification_ssl=True, **backend_options): self._pusher_client = PusherClient( app_id, key, secret, ssl, host, port, timeout, cluster, encryption_master_key, json_encoder, json_decoder, backend, **backend_options) self._authentication_client = AuthenticationClient( app_id, key, secret, ssl, host, port, timeout, cluster, encryption_master_key, json_encoder, json_decoder, backend, **backend_options) self._notification_client = NotificationClient( app_id, key, secret, notification_ssl, notification_host, port, timeout, cluster, encryption_master_key, json_encoder, json_decoder, backend, **backend_options)
def test_trigger_with_private_encrypted_channel_success(self): # instantiate a new client configured with the master encryption key master_key = b'8tW5FQLniQ1sBQFwrw7t6TVEsJZd10yY' master_key_base64 = base64.b64encode(master_key) pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key_base64=master_key_base64, ssl=True) # trigger a request to a private-encrypted channel and capture the request to assert equality chan = "private-encrypted-tst" payload = {"message": "hello worlds"} event_name = 'some_event' request = pc.trigger.make_request(chan, event_name, payload) # simulate the same encryption process and check equality chan = ensure_binary(chan, "chan") shared_secret = generate_shared_secret(chan, master_key) box = nacl.secret.SecretBox(shared_secret) nonce_b64 = json.loads(request.params["data"])["nonce"].encode("utf-8") nonce = base64.b64decode(nonce_b64) encrypted = box.encrypt( json.dumps(payload, ensure_ascii=False).encode("utf'-8"), nonce) # obtain the ciphertext cipher_text = encrypted.ciphertext # encode cipertext to base64 cipher_text_b64 = base64.b64encode(cipher_text) # format expected output json_dumped = json.dumps({ "nonce": nonce_b64.decode("utf-8"), "ciphertext": cipher_text_b64.decode("utf-8") }) expected_params = { u'channels': [u'private-encrypted-tst'], u'data': json_dumped, u'name': u'some_event' } self.assertEqual(request.params, expected_params)
def test_validate_webhook_bad_types(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') pusher.validate_webhook(u'key', u'signature', u'body') # These things are meant to be human readable, so enforcing being text is # sensible. with mock.patch('time.time') as time_mock: self.assertRaises( TypeError, lambda: pusher.validate_webhook(4, u'signature', u'body')) self.assertRaises( TypeError, lambda: pusher.validate_webhook(u'key', 4, u'body')) self.assertRaises( TypeError, lambda: pusher.validate_webhook(u'key', u'signature', 4)) time_mock.assert_not_called()
def test_authenticate_for_presence_channels(self): pusher = PusherClient.from_url(u'http://*****:*****@host/apps/4') custom_data = {u'user_id': u'fred', u'user_info': {u'key': u'value'}} expected = { u'auth': u"foo:e80ba6439492c2113022c39297a87a948de14061cc67b5788e045645a68b8ccd", u'channel_data': u"{\"user_id\":\"fred\",\"user_info\":{\"key\":\"value\"}}" } with mock.patch('json.dumps', return_value=expected[u'channel_data']) as dumps_mock: actual = pusher.authenticate(u'presence-channel', u'345.43245', custom_data) self.assertEqual(actual, expected) dumps_mock.assert_called_once_with(custom_data, cls=None)
def test_trigger_with_public_channel_with_encryption_master_key_specified_success( self): json_dumped = u'{"message": "something"}' pc = PusherClient(app_id=u'4', key=u'key', secret=u'secret', encryption_master_key_base64= u'OHRXNUZRTG5pUTFzQlFGd3J3N3Q2VFZFc0paZDEweVk=', ssl=True) with mock.patch('json.dumps', return_value=json_dumped) as json_dumps_mock: request = pc.trigger.make_request(u'donuts', u'some_event', {u'message': u'hello worlds'}) expected_params = { u'channels': [u'donuts'], u'data': json_dumped, u'name': u'some_event' } self.assertEqual(request.params, expected_params)
def setUp(self): self.pusher_client = PusherClient(app_id=u'4', key=u'key', secret=u'secret', host=u'somehost')
class Pusher(object): """Client for the Pusher HTTP API. This client supports various backend adapters to support various http libraries available in the python ecosystem. :param app_id: a pusher application identifier :param key: a pusher application key :param secret: a pusher application secret token :param ssl: Whenever to use SSL or plain HTTP :param host: Used for custom host destination :param port: Used for custom port destination :param timeout: Request timeout (in seconds) :param cluster: Convention for other clusters than the main Pusher-one. Eg: 'eu' will resolve to the api-eu.pusherapp.com host :param backend: an http adapter class (AsyncIOBackend, RequestsBackend, SynchronousBackend, TornadoBackend) :param backend_options: additional backend """ def __init__( self, app_id, key, secret, ssl=True, host=None, port=None, timeout=5, cluster=None, json_encoder=None, json_decoder=None, backend=None, notification_host=None, notification_ssl=True, **backend_options): self._pusher_client = PusherClient( app_id, key, secret, ssl, host, port, timeout, cluster, json_encoder, json_decoder, backend, **backend_options) self._authentication_client = AuthenticationClient( app_id, key, secret, ssl, host, port, timeout, cluster, json_encoder, json_decoder, backend, **backend_options) self._notification_client = NotificationClient( app_id, key, secret, notification_ssl, notification_host, port, timeout, cluster, json_encoder, json_decoder, backend, **backend_options) @classmethod def from_url(cls, url, **options): """Alternative constructor that extracts the information from a URL. :param url: String containing a URL Usage:: >> from pusher import Pusher >> p = Pusher.from_url("http://*****:*****@api.pusher.com/apps/432") """ m = pusher_url_re.match(ensure_text(url, "url")) if not m: raise Exception("Unparsable url: %s" % url) ssl = m.group(1) == 'https' options_ = { 'key': m.group(2), 'secret': m.group(3), 'host': m.group(4), 'app_id': m.group(5), 'ssl': ssl} options_.update(options) return cls(**options_) @classmethod def from_env(cls, env='PUSHER_URL', **options): """Alternative constructor that extracts the information from an URL stored in an environment variable. The pusher heroku addon will set the PUSHER_URL automatically when installed for example. :param env: Name of the environment variable Usage:: >> from pusher import Pusher >> c = Pusher.from_env("PUSHER_URL") """ val = os.environ.get(env) if not val: raise Exception("Environment variable %s not found" % env) return cls.from_url(val, **options) @doc_string(PusherClient.trigger.__doc__) def trigger(self, channels, event_name, data, socket_id=None): return self._pusher_client.trigger( channels, event_name, data, socket_id) @doc_string(PusherClient.trigger_batch.__doc__) def trigger_batch(self, batch=[], already_encoded=False): return self._pusher_client.trigger_batch(batch, already_encoded) @doc_string(PusherClient.channels_info.__doc__) def channels_info(self, prefix_filter=None, attributes=[]): return self._pusher_client.channels_info(prefix_filter, attributes) @doc_string(PusherClient.channel_info.__doc__) def channel_info(self, channel, attributes=[]): return self._pusher_client.channel_info(channel, attributes) @doc_string(PusherClient.users_info.__doc__) def users_info(self, channel): return self._pusher_client.users_info(channel) @doc_string(AuthenticationClient.authenticate.__doc__) def authenticate(self, channel, socket_id, custom_data=None): return self._authentication_client.authenticate( channel, socket_id, custom_data) @doc_string(AuthenticationClient.validate_webhook.__doc__) def validate_webhook(self, key, signature, body): return self._authentication_client.validate_webhook( key, signature, body) @doc_string(NotificationClient.notify.__doc__) def notify(self, interest, notification): return self._notification_client.notify(interest, notification)
def test_encryption_master_key_should_be_text(self): PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=u'eu', encryption_master_key="8tW5FQLniQ1sBQFwrw7t6TVEsJZd10yY") self.assertRaises(TypeError, lambda: PusherClient(app_id=u'4', key=u'key', secret=u'secret', ssl=True, cluster=4, encryption_master_key=48762478647865374856347856888764 ))