Skip to content

Commit

Permalink
Add user authentication and the possibility to trigger/send a message…
Browse files Browse the repository at this point in the history
… to a specific user
  • Loading branch information
Anderson Rocha committed Aug 17, 2023
1 parent 239d67b commit f09845c
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 120 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ var/

pip-log.txt
pip-delete-this-directory.txt

.idea
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ In order to use this library, you need to have a free account on <http://pusher.
- [Getting started](#getting-started)
- [Configuration](#configuration)
- [Triggering Events](#triggering-events)
- [Send a message to a specific user](#send-a-message-to-a-specific-user)
- [Querying Application State](#querying-application-state)
- [Getting Information For All Channels](#getting-information-for-all-channels)
- [Getting Information For A Specific Channel](#getting-information-for-a-specific-channel)
- [Getting User Information For A Presence Channel](#getting-user-information-for-a-presence-channel)
- [Authenticating Channel Subscription](#authenticating-channel-subscription)
- [Authenticating User](#authenticating-user)
- [Terminating User Connections](#terminating-user-connections)
- [End-to-end Encryption](#end-to-end-encryption)
- [Receiving Webhooks](#receiving-webhooks)
Expand Down Expand Up @@ -164,6 +166,30 @@ pusher_client.trigger_batch([
])
```

### Send a message to a specific user

#### `Pusher::send_to_user`

|Argument |Description |
|:-:|:-:|
|user_id `String` |**Required** <br> The user id |
|event `String`| **Required** <br> The name of the event you wish to trigger. |
|data `JSONable data` | **Required** <br> The event's payload |

|Return Values |Description |
|:-:|:-:|
|buffered_events `Dict` | A parsed response that includes the event_id for each event published to a channel. See example. |

`Pusher::trigger` will throw a `TypeError` if called with parameters of the wrong type; or a `ValueError` if called on more than 100 channels, with an event name longer than 200 characters, or with more than 10240 characters of data (post JSON serialisation).

##### Example

This call will send a message to the user with id `'123'`.

```python
pusher_client.send_to_user( u'123', u'some_event', {u'message': u'hello worlds'})
```

## Querying Application State

### Getting Information For All Channels
Expand Down Expand Up @@ -288,6 +314,38 @@ auth = pusher_client.authenticate(
# return `auth` as a response
```

## Authenticating User

#### `Pusher::authenticate_user`

To authenticate users on Pusher Channels on your application, you can use the authenticate_user function:

|Argument |Description |
|:-:|:-:|
|socket_id `String` | **Required**<br> The channel's socket_id, also sent to you in the POST request |
|user_data `Dict` |**Required for presence channels** <br> This will be a dictionary containing the data you want associated with a user. An `"id"` key is *required* |

|Return Values |Description |
|:-:|:-:|
|response `Dict` | A dictionary to send as a response to the authentication request.|

For more information see [authenticating users](https://pusher.com/docs/channels/server_api/authenticating-users/).

##### Example

###### User Authentication

```python
auth = pusher_client.authenticate_user(
socket_id=u"1234.12",
user_data = {
u'id': u'123',
u'name': u'John Smith'
}
)
# return `auth` as a response
```

## Terminating user connections

TIn order to terminate a user's connections, the user must have been authenticated. Check the [Server user authentication docs](http://pusher.com/docs/authenticating_users) for the information on how to create a user authentication endpoint.
Expand Down Expand Up @@ -406,9 +464,11 @@ Feature | Supported
-------------------------------------------| :-------:
Trigger event on single channel | *&#10004;*
Trigger event on multiple channels | *&#10004;*
Trigger event to a specifc user | *&#10004;*
Excluding recipients from events | *&#10004;*
Authenticating private channels | *&#10004;*
Authenticating presence channels | *&#10004;*
Authenticating users | *&#10004;*
Get the list of channels in an application | *&#10004;*
Get the state of a single channel | *&#10004;*
Get a list of users in a presence channel | *&#10004;*
Expand Down
58 changes: 39 additions & 19 deletions pusher/authentication_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
ensure_binary,
validate_channel,
validate_socket_id,
validate_user_data,
channel_name_re
)
)

from pusher.client import Client
from pusher.http import GET, POST, Request, request_method
Expand All @@ -31,21 +32,21 @@

class AuthenticationClient(Client):
def __init__(
self,
app_id,
key,
secret,
ssl=True,
host=None,
port=None,
timeout=5,
cluster=None,
encryption_master_key=None,
encryption_master_key_base64=None,
json_encoder=None,
json_decoder=None,
backend=None,
**backend_options):
self,
app_id,
key,
secret,
ssl=True,
host=None,
port=None,
timeout=5,
cluster=None,
encryption_master_key=None,
encryption_master_key_base64=None,
json_encoder=None,
json_decoder=None,
backend=None,
**backend_options):

super(AuthenticationClient, self).__init__(
app_id,
Expand All @@ -63,7 +64,6 @@ def __init__(
backend,
**backend_options)


def authenticate(self, channel, socket_id, custom_data=None):
"""Used to generate delegated client subscription token.
Expand All @@ -89,7 +89,7 @@ def authenticate(self, channel, socket_id, custom_data=None):
signature = sign(self.secret, string_to_sign)

auth = "%s:%s" % (self.key, signature)
response_payload = { "auth": auth }
response_payload = {"auth": auth}

if is_encrypted_channel(channel):
shared_secret = generate_shared_secret(
Expand All @@ -102,6 +102,25 @@ def authenticate(self, channel, socket_id, custom_data=None):

return response_payload

def authenticate_user(self, socket_id, user_data=None):
"""Creates a user authentication signature.
:param socket_id: id of the socket that requires authorization
:param user_data: used to provide user info
"""
validate_user_data(user_data)
socket_id = validate_socket_id(socket_id)

user_data_encoded = json.dumps(user_data, cls=self._json_encoder)

string_to_sign = "%s::user::%s" % (socket_id, user_data_encoded)

signature = sign(self.secret, string_to_sign)

auth_response = "%s:%s" % (self.key, signature)
response_payload = {"auth": auth_response, 'user_data': user_data_encoded}

return response_payload

def validate_webhook(self, key, signature, body):
"""Used to validate incoming webhook messages. When used it guarantees
Expand Down Expand Up @@ -131,7 +150,8 @@ def validate_webhook(self, key, signature, body):
if not time_ms:
return None

if abs(time.time()*1000 - time_ms) > 300000:
if abs(time.time() * 1000 - time_ms) > 300000:
return None

return body_data

42 changes: 23 additions & 19 deletions pusher/pusher.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ class Pusher(object):
:param backend_options: additional backend
"""
def __init__(
self,
app_id,
key,
secret,
ssl=True,
host=None,
port=None,
timeout=5,
cluster=None,
encryption_master_key=None,
encryption_master_key_base64=None,
json_encoder=None,
json_decoder=None,
backend=None,
**backend_options):
self,
app_id,
key,
secret,
ssl=True,
host=None,
port=None,
timeout=5,
cluster=None,
encryption_master_key=None,
encryption_master_key_base64=None,
json_encoder=None,
json_decoder=None,
backend=None,
**backend_options):

self._pusher_client = PusherClient(
app_id,
Expand Down Expand Up @@ -93,7 +93,6 @@ def __init__(
backend,
**backend_options)


@classmethod
def from_url(cls, url, **options):
"""Alternative constructor that extracts the information from a URL.
Expand Down Expand Up @@ -123,7 +122,6 @@ def from_url(cls, url, **options):

return cls(**options_)


@classmethod
def from_env(cls, env='PUSHER_URL', **options):
"""Alternative constructor that extracts the information from an URL
Expand All @@ -143,12 +141,14 @@ def from_env(cls, env='PUSHER_URL', **options):

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.send_to_user.__doc__)
def send_to_user(self, user_id, event_name, data):
return self._pusher_client.send_to_user(user_id, event_name, data)

@doc_string(PusherClient.trigger_batch.__doc__)
def trigger_batch(self, batch=[], already_encoded=False):
Expand All @@ -158,7 +158,6 @@ def trigger_batch(self, batch=[], already_encoded=False):
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)
Expand All @@ -176,6 +175,11 @@ def authenticate(self, channel, socket_id, custom_data=None):
return self._authentication_client.authenticate(
channel, socket_id, custom_data)

@doc_string(AuthenticationClient.authenticate_user.__doc__)
def authenticate_user(self, socket_id, user_data=None):
return self._authentication_client.authenticate_user(
socket_id, user_data
)

@doc_string(AuthenticationClient.validate_webhook.__doc__)
def validate_webhook(self, key, signature, body):
Expand Down
Loading

0 comments on commit f09845c

Please sign in to comment.