Example #1
0
    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)
Example #2
0
    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'})
Example #3
0
    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')
Example #4
0
    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))
Example #5
0
    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)
Example #11
0
    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)
Example #15
0
    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)
Example #16
0
    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)
Example #19
0
    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)
Example #20
0
 def setUp(self):
     self.pusher_client = PusherClient(app_id=u'4',
                                       key=u'key',
                                       secret=u'secret',
                                       host=u'somehost')
Example #21
0
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 ))