Skip to content

Commit f912973

Browse files
Terminate user connections feature (#190)
* Terminate user connections feature * Bump to version 3.3.0 * Update readme Co-authored-by: Pusher CI <[email protected]>
1 parent 3d1dcfa commit f912973

File tree

8 files changed

+74
-7
lines changed

8 files changed

+74
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.3.0
4+
5+
- [ADDED] terminate_user_connections method
6+
37
## 3.2.0 2022-01-25
48

59
* [FIXED] An issue where payload size wasn't being calculated properly

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ In order to use this library, you need to have a free account on <http://pusher.
2828
- [Getting Information For A Specific Channel](#getting-information-for-a-specific-channel)
2929
- [Getting User Information For A Presence Channel](#getting-user-information-for-a-presence-channel)
3030
- [Authenticating Channel Subscription](#authenticating-channel-subscription)
31+
- [Terminating User Connections](#terminating-user-connections)
3132
- [End-to-end Encryption](#end-to-end-encryption)
3233
- [Receiving Webhooks](#receiving-webhooks)
3334
- [Request Library Configuration](#request-library-configuration)
@@ -287,6 +288,18 @@ auth = pusher_client.authenticate(
287288
# return `auth` as a response
288289
```
289290

291+
## Terminating user connections
292+
293+
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.
294+
295+
To terminate all connections established by a given user, you can use the `terminate_user_connections` function:
296+
297+
```python
298+
pusher_client.terminate_user_connections(userId)
299+
```
300+
301+
Please note, that it only terminates the user's active connections. This means, if nothing else is done, the user will be able to reconnect. For more information see: [Terminating user connections docs](https://pusher.com/docs/channels/server_api/terminating-user-connections/).
302+
290303
## End to End Encryption
291304

292305
This library supports end to end encryption of your private channels. This
@@ -400,6 +413,7 @@ Get the list of channels in an application | *&#10004;*
400413
Get the state of a single channel | *&#10004;*
401414
Get a list of users in a presence channel | *&#10004;*
402415
WebHook validation | *&#10004;*
416+
Terminate user connections | *&#10004;*
403417
Heroku add-on support | *&#10004;*
404418
Debugging & Logging | *&#10004;*
405419
Cluster configuration | *&#10004;*

pusher/pusher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ def trigger(self, channels, event_name, data, socket_id=None):
154154
def trigger_batch(self, batch=[], already_encoded=False):
155155
return self._pusher_client.trigger_batch(batch, already_encoded)
156156

157-
158157
@doc_string(PusherClient.channels_info.__doc__)
159158
def channels_info(self, prefix_filter=None, attributes=[]):
160159
return self._pusher_client.channels_info(prefix_filter, attributes)
@@ -164,11 +163,13 @@ def channels_info(self, prefix_filter=None, attributes=[]):
164163
def channel_info(self, channel, attributes=[]):
165164
return self._pusher_client.channel_info(channel, attributes)
166165

167-
168166
@doc_string(PusherClient.users_info.__doc__)
169167
def users_info(self, channel):
170168
return self._pusher_client.users_info(channel)
171169

170+
@doc_string(PusherClient.terminate_user_connections.__doc__)
171+
def terminate_user_connections(self, user_id):
172+
return self._pusher_client.terminate_user_connections(user_id)
172173

173174
@doc_string(AuthenticationClient.authenticate.__doc__)
174175
def authenticate(self, channel, socket_id, custom_data=None):

pusher/pusher_client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
ensure_text,
2525
validate_channel,
2626
validate_socket_id,
27+
validate_user_id,
2728
join_attributes,
2829
data_to_string)
2930

@@ -113,7 +114,6 @@ def trigger(self, channels, event_name, data, socket_id=None):
113114

114115
return Request(self, POST, "/apps/%s/events" % self.app_id, params)
115116

116-
117117
@request_method
118118
def trigger_batch(self, batch=[], already_encoded=False):
119119
"""Trigger multiple events with a single HTTP call.
@@ -142,7 +142,6 @@ def trigger_batch(self, batch=[], already_encoded=False):
142142
return Request(
143143
self, POST, "/apps/%s/batch_events" % self.app_id, params)
144144

145-
146145
@request_method
147146
def channels_info(self, prefix_filter=None, attributes=[]):
148147
"""Get information on multiple channels, see:
@@ -187,3 +186,9 @@ def users_info(self, channel):
187186

188187
return Request(
189188
self, GET, "/apps/%s/channels/%s/users" % (self.app_id, channel))
189+
190+
@request_method
191+
def terminate_user_connections(self, user_id):
192+
validate_user_id(user_id)
193+
return Request(
194+
self, POST, "/users/{}/terminate_connections".format(user_id), {})

pusher/util.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ def is_base64(s):
6767
except Exception as e:
6868
return False
6969

70+
def validate_user_id(user_id):
71+
user_id = ensure_text(user_id, "user_id")
72+
73+
length = len(user_id)
74+
if length == 0:
75+
raise ValueError("User id is empty")
76+
77+
if length > 200:
78+
raise ValueError("User id too long: '{}'".format(user_id))
79+
80+
if not channel_name_re.match(user_id):
81+
raise ValueError("Invalid user id: '{}'".format(user_id))
82+
83+
return user_id
84+
7085
def validate_channel(channel):
7186
channel = ensure_text(channel, "channel")
7287

pusher/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Don't change the format of this line: the version is extracted by ../setup.py
2-
VERSION = '3.2.0'
2+
VERSION = '3.3.0'

pusher_tests/test_pusher_client.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import base64
1616

1717
from pusher.pusher_client import PusherClient
18-
from pusher.http import GET
18+
from pusher.http import GET, POST
1919
from pusher.crypto import *
2020

2121
try:
@@ -57,7 +57,7 @@ def test_trigger_with_channels_list_success_case(self):
5757
request = self.pusher_client.trigger.make_request([u'some_channel'], u'some_event', {u'message': u'hello world'})
5858

5959
self.assertEqual(request.path, u'/apps/4/events')
60-
self.assertEqual(request.method, u'POST')
60+
self.assertEqual(request.method, POST)
6161

6262
expected_params = {
6363
u'channels': [u'some_channel'],
@@ -366,6 +366,16 @@ def test_user_info_success_case(self):
366366
self.assertEqual(request.path, u'/apps/4/channels/presence-channel/users')
367367
self.assertEqual(request.params, {})
368368

369+
def test_terminate_user_connection_success_case(self):
370+
request = self.pusher_client.terminate_user_connections.make_request('123')
371+
self.assertEqual(request.path, u'/users/123/terminate_connections')
372+
self.assertEqual(request.method, POST)
373+
self.assertEqual(request.params, {})
374+
375+
def test_terminate_user_connection_fail_case_invalid_user_id(self):
376+
with self.assertRaises(ValueError):
377+
self.pusher_client.terminate_user_connections("")
378+
369379

370380
if __name__ == '__main__':
371381
unittest.main()

pusher_tests/test_util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import unittest
2+
3+
import pusher.util
4+
5+
class TestUtil(unittest.TestCase):
6+
def test_validate_user_id(self):
7+
valid_user_ids = ["1", "12", "abc", "ab12", "ABCDEFG1234"]
8+
invalid_user_ids = ["", "x" * 201, "abc%&*"]
9+
10+
for user_id in valid_user_ids:
11+
self.assertEqual(user_id, pusher.util.validate_user_id(user_id))
12+
13+
for user_id in invalid_user_ids:
14+
with self.assertRaises(ValueError):
15+
pusher.util.validate_user_id(user_id)
16+
17+
if __name__ == '__main__':
18+
unittest.main()

0 commit comments

Comments
 (0)