Skip to content

Commit 1ece87c

Browse files
PubNub SDK v4.6.0 release.
1 parent 4e071ee commit 1ece87c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3395
-99
lines changed

.pubnub.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
name: python
2-
version: 4.5.4
2+
version: 4.6.0
33
schema: 1
44
scm: github.com/pubnub/python
55
changelog:
6+
- version: v4.6.0
7+
date: Oct 22, 2020
8+
changes:
9+
-
10+
text: "File Upload added to the Python SDK."
11+
type: feature
612
- version: v4.5.4
713
date: Sep 29, 2020
814
changes:
@@ -16,7 +22,7 @@ changelog:
1622
date: Aug 10, 2020
1723
changes:
1824
-
19-
text: "Allocating separate thread that basically waits certain amount of time to clean telemetry data is a waste of memory/OS data strucutres. Clening mentioned data can be incorporated into regular logic."
25+
text: "Allocating separate thread that basically waits a certain amount of time to clean telemetry data is a waste of memory/OS data structures. Cleaning mentioned data can be incorporated into regular logic."
2026
type: improvement
2127
- version: v4.5.2
2228
date: May 29, 2020
@@ -62,7 +68,7 @@ changelog:
6268
date: Dec 24, 2019
6369
changes:
6470
- type: improvement
65-
text: Introduced delete permission to Grant endpoint. Migrated to v2 enpdoints for old PAM methods.
71+
text: Introduced delete permission to Grant endpoint. Migrated to v2 endpoints for old PAM methods.
6672
- type: feature
6773
text: Added TokenManager and GrantToken method.
6874
- type: improvement
@@ -121,7 +127,7 @@ changelog:
121127
date: Jun 14, 2017
122128
changes:
123129
- type: improvement
124-
text: Added deamon option for PNConfig
130+
text: Added daemon option for PNConfig
125131
- version: v4.0.12
126132
date:
127133
changes:
@@ -191,7 +197,7 @@ changelog:
191197
- type: improvement
192198
text: Adjusting maximum pool size for requests installations
193199
- type: improvement
194-
text: Adding Publsher UUID
200+
text: Adding Publisher UUID
195201
- version: v4.0.1
196202
date: Nov 8, 2016
197203
changes:

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [v4.6.0](https://github.com/pubnub/python/releases/tag/v4.6.0)
2+
3+
[Full Changelog](https://github.com/pubnub/python/compare/v4.5.4...v4.6.0)
4+
5+
- 🌟️ File Upload added to the Python SDK.
6+
- ⭐️️ Fix spelling typos in `.pubnub.yml` file. Addresses the following PRs from [@samiahmedsiddiqui](https://github.com/samiahmedsiddiqui): [#92](https://github.com/pubnub/python/pull/92).
7+
18
## [v4.5.4](https://github.com/pubnub/python/releases/tag/v4.5.4)
29

310
[Full Changelog](https://github.com/pubnub/python/compare/v4.5.3...v4.5.4)

pubnub/callbacks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ def membership(self, pubnub, membership):
3737
def message_action(self, pubnub, message_action):
3838
pass
3939

40+
def file(self, pubnub, file_message):
41+
pass
42+
4043

4144
class ReconnectionCallback(object):
4245
@abstractmethod

pubnub/crypto.py

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import hashlib
22
import json
33
import sys
4+
import random
45

56
from .crypto_core import PubNubCrypto
67
from Cryptodome.Cipher import AES
8+
from Cryptodome.Util.Padding import pad, unpad
79

810
Initial16bytes = '0123456789012345'
911

@@ -26,31 +28,67 @@
2628

2729

2830
class PubNubCryptodome(PubNubCrypto):
29-
def encrypt(self, key, msg):
31+
def __init__(self, pubnub_config):
32+
self.pubnub_configuration = pubnub_config
33+
34+
def encrypt(self, key, msg, use_random_iv=False):
3035
secret = self.get_secret(key)
36+
initialization_vector = self.get_initialization_vector(use_random_iv)
3137

3238
if v == 3:
33-
cipher = AES.new(bytes(secret[0:32], 'utf-8'), AES.MODE_CBC, bytes(Initial16bytes, 'utf-8'))
34-
return encodebytes(cipher.encrypt(self.pad(msg.encode('utf-8')))).decode('utf-8').replace("\n", "")
39+
cipher = AES.new(bytes(secret[0:32], 'utf-8'), AES.MODE_CBC, bytes(initialization_vector, 'utf-8'))
40+
encrypted_message = cipher.encrypt(self.pad(msg.encode('utf-8')))
41+
msg_with_iv = self.append_random_iv(encrypted_message, use_random_iv, bytes(initialization_vector, "utf-8"))
42+
43+
return encodebytes(msg_with_iv).decode('utf-8').replace("\n", "")
44+
3545
else:
36-
cipher = AES.new(secret[0:32], AES.MODE_CBC, Initial16bytes)
37-
return encodestring(cipher.encrypt(self.pad(msg))).replace("\n", "")
46+
cipher = AES.new(secret[0:32], AES.MODE_CBC, initialization_vector)
47+
encrypted_message = cipher.encrypt(self.pad(msg))
48+
msg_with_iv = self.append_random_iv(encrypted_message, use_random_iv, initialization_vector)
49+
return encodestring(msg_with_iv).replace("\n", "")
3850

39-
def decrypt(self, key, msg):
51+
def decrypt(self, key, msg, use_random_iv=False):
4052
secret = self.get_secret(key)
4153

4254
if v == 3:
43-
cipher = AES.new(bytes(secret[0:32], 'utf-8'), AES.MODE_CBC, bytes(Initial16bytes, 'utf-8'))
44-
plain = self.depad((cipher.decrypt(decodebytes(msg.encode('utf-8')))).decode('utf-8'))
55+
decoded_message = decodebytes(msg.encode("utf-8"))
56+
initialization_vector, extracted_message = self.extract_random_iv(decoded_message, use_random_iv)
57+
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, initialization_vector)
58+
plain = self.depad((cipher.decrypt(extracted_message)).decode('utf-8'))
59+
4560
else:
46-
cipher = AES.new(secret[0:32], AES.MODE_CBC, Initial16bytes)
47-
plain = self.depad(cipher.decrypt(decodestring(msg)))
61+
decoded_message = decodestring(msg)
62+
initialization_vector, extracted_message = self.extract_random_iv(decoded_message, use_random_iv)
63+
cipher = AES.new(secret[0:32], AES.MODE_CBC, initialization_vector)
64+
plain = self.depad(cipher.decrypt(extracted_message))
4865

4966
try:
5067
return json.loads(plain)
5168
except Exception:
5269
return plain
5370

71+
def append_random_iv(self, message, use_random_iv, initialization_vector):
72+
if self.pubnub_configuration.use_random_initialization_vector or use_random_iv:
73+
return initialization_vector + message
74+
else:
75+
return message
76+
77+
def extract_random_iv(self, message, use_random_iv):
78+
if self.pubnub_configuration.use_random_initialization_vector or use_random_iv:
79+
return message[0:16], message[16:]
80+
else:
81+
if v == 3:
82+
return bytes(Initial16bytes, "utf-8"), message
83+
else:
84+
return Initial16bytes, message
85+
86+
def get_initialization_vector(self, use_random_iv):
87+
if self.pubnub_configuration.use_random_initialization_vector or use_random_iv:
88+
return "{0:016}".format(random.randint(0, 9999999999999999))
89+
else:
90+
return Initial16bytes
91+
5492
def pad(self, msg, block_size=16):
5593
padding = block_size - (len(msg) % block_size)
5694

@@ -67,3 +105,32 @@ def get_secret(self, key):
67105
return hashlib.sha256(key.encode("utf-8")).hexdigest()
68106
else:
69107
return hashlib.sha256(key).hexdigest()
108+
109+
110+
class PubNubFileCrypto(PubNubCryptodome):
111+
def encrypt(self, key, file):
112+
secret = self.get_secret(key)
113+
initialization_vector = self.get_initialization_vector(use_random_iv=True)
114+
115+
if v == 3:
116+
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, bytes(initialization_vector, 'utf-8'))
117+
initialization_vector = bytes(initialization_vector, 'utf-8')
118+
else:
119+
cipher = AES.new(secret[0:32], AES.MODE_CBC, initialization_vector)
120+
121+
return self.append_random_iv(
122+
cipher.encrypt(pad(file, 16)),
123+
use_random_iv=True,
124+
initialization_vector=initialization_vector
125+
)
126+
127+
def decrypt(self, key, file):
128+
secret = self.get_secret(key)
129+
initialization_vector, extracted_file = self.extract_random_iv(file, use_random_iv=True)
130+
131+
if v == 3:
132+
cipher = AES.new(bytes(secret[0:32], "utf-8"), AES.MODE_CBC, initialization_vector)
133+
else:
134+
cipher = AES.new(secret[0:32], AES.MODE_CBC, initialization_vector)
135+
136+
return unpad(cipher.decrypt(extracted_file), 16)

pubnub/endpoints/endpoint.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
from pubnub import utils
66
from pubnub.enums import PNStatusCategory, PNOperationType
7-
from pubnub.errors import PNERR_SUBSCRIBE_KEY_MISSING, PNERR_PUBLISH_KEY_MISSING, PNERR_CHANNEL_OR_GROUP_MISSING, \
8-
PNERR_SECRET_KEY_MISSING, PNERR_CHANNEL_MISSING
7+
from pubnub.errors import (
8+
PNERR_SUBSCRIBE_KEY_MISSING, PNERR_PUBLISH_KEY_MISSING, PNERR_CHANNEL_OR_GROUP_MISSING,
9+
PNERR_SECRET_KEY_MISSING, PNERR_CHANNEL_MISSING, PNERR_FILE_OBJECT_MISSING,
10+
PNERR_FILE_ID_MISSING, PNERR_FILE_NAME_MISSING
11+
)
912
from pubnub.exceptions import PubNubException
1013
from pubnub.models.consumer.common import PNStatus
1114
from pubnub.models.consumer.pn_error_data import PNErrorData
@@ -78,6 +81,24 @@ def affected_channels(self):
7881
def affected_channels_groups(self):
7982
return None
8083

84+
def allow_redirects(self):
85+
return True
86+
87+
def use_base_path(self):
88+
return True
89+
90+
def request_headers(self):
91+
if self.http_method() == "POST":
92+
return {"Content-type": "application/json"}
93+
else:
94+
return {}
95+
96+
def build_file_upload_request(self):
97+
return
98+
99+
def non_json_response(self):
100+
return False
101+
81102
def options(self):
82103
return RequestOptions(
83104
path=self.build_path(),
@@ -90,7 +111,13 @@ def options(self):
90111
create_exception=self.create_exception,
91112
operation_type=self.operation_type(),
92113
data=self.build_data(),
93-
sort_arguments=self._sort_params)
114+
files=self.build_file_upload_request(),
115+
sort_arguments=self._sort_params,
116+
allow_redirects=self.allow_redirects(),
117+
use_base_path=self.use_base_path(),
118+
request_headers=self.request_headers(),
119+
non_json_response=self.non_json_response()
120+
)
94121

95122
def sync(self):
96123
self.validate_params()
@@ -202,6 +229,18 @@ def validate_publish_key(self):
202229
if self.pubnub.config.publish_key is None or len(self.pubnub.config.publish_key) == 0:
203230
raise PubNubException(pn_error=PNERR_PUBLISH_KEY_MISSING)
204231

232+
def validate_file_object(self):
233+
if not self._file_object:
234+
raise PubNubException(pn_error=PNERR_FILE_OBJECT_MISSING)
235+
236+
def validate_file_name(self):
237+
if not self._file_name:
238+
raise PubNubException(pn_error=PNERR_FILE_NAME_MISSING)
239+
240+
def validate_file_id(self):
241+
if not self._file_id:
242+
raise PubNubException(pn_error=PNERR_FILE_ID_MISSING)
243+
205244
def create_status(self, category, response, response_info, exception):
206245
if response_info is not None:
207246
assert isinstance(response_info, ResponseInfo)

pubnub/endpoints/file_operations/__init__.py

Whitespace-only changes.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from pubnub.endpoints.file_operations.file_based_endpoint import FileOperationEndpoint
2+
from pubnub.enums import HttpMethod, PNOperationType
3+
from pubnub import utils
4+
from pubnub.models.consumer.file import PNDeleteFileResult
5+
6+
7+
class DeleteFile(FileOperationEndpoint):
8+
DELETE_FILE_URL = "/v1/files/%s/channels/%s/files/%s/%s"
9+
10+
def __init__(self, pubnub):
11+
FileOperationEndpoint.__init__(self, pubnub)
12+
self._file_id = None
13+
self._file_name = None
14+
15+
def build_path(self):
16+
return DeleteFile.DELETE_FILE_URL % (
17+
self.pubnub.config.subscribe_key,
18+
utils.url_encode(self._channel),
19+
self._file_id,
20+
self._file_name
21+
)
22+
23+
def file_id(self, file_id):
24+
self._file_id = file_id
25+
return self
26+
27+
def file_name(self, file_name):
28+
self._file_name = file_name
29+
return self
30+
31+
def http_method(self):
32+
return HttpMethod.DELETE
33+
34+
def custom_params(self):
35+
return {}
36+
37+
def is_auth_required(self):
38+
return True
39+
40+
def validate_params(self):
41+
self.validate_subscribe_key()
42+
self.validate_channel()
43+
self.validate_file_name()
44+
self.validate_file_id()
45+
46+
def create_response(self, envelope):
47+
return PNDeleteFileResult(envelope)
48+
49+
def operation_type(self):
50+
return PNOperationType.PNDeleteFileOperation
51+
52+
def name(self):
53+
return "Delete file"

0 commit comments

Comments
 (0)