sendbee-api 1.7.0__tar.gz → 1.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/PKG-INFO +23 -29
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/README.md +1 -19
- sendbee_api-1.7.2/pyproject.toml +46 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/bind.py +11 -1
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/constants.py +21 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/models.py +2 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/query_params.py +2 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/debug.py +8 -1
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/PKG-INFO +24 -30
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/SOURCES.txt +11 -2
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/requires.txt +6 -1
- sendbee_api-1.7.2/tests/test_auth.py +110 -0
- sendbee_api-1.7.2/tests/test_bind_request.py +476 -0
- sendbee_api-1.7.2/tests/test_client.py +67 -0
- sendbee_api-1.7.2/tests/test_endpoints_smoke.py +177 -0
- sendbee_api-1.7.2/tests/test_fields.py +105 -0
- sendbee_api-1.7.2/tests/test_formatter.py +79 -0
- sendbee_api-1.7.2/tests/test_models.py +99 -0
- sendbee_api-1.7.2/tests/test_query_params.py +59 -0
- sendbee_api-1.7.2/tests/test_response.py +112 -0
- sendbee_api-1.7.0/setup.py +0 -47
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/LICENSE +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/auth.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/models.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/query_params.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/models.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/query_params.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/exceptions.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/fields.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/formatter.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/models.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/query_params.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/models.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/query_params.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/response.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/__init__.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/client.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/models.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/query_params.py +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/dependency_links.txt +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/top_level.txt +0 -0
- {sendbee_api-1.7.0 → sendbee_api-1.7.2}/setup.cfg +0 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sendbee_api
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.2
|
|
4
4
|
Summary: Python client SDK for Sendbee Public API
|
|
5
|
-
|
|
6
|
-
Author: Sendbee ltd
|
|
7
|
-
Author-email: info@sendbee.io
|
|
5
|
+
Author-email: Sendbee ltd <info@sendbee.io>
|
|
8
6
|
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sendbee/sendbee-python-client
|
|
9
8
|
Project-URL: Source, https://github.com/sendbee/sendbee-python-client
|
|
10
|
-
Keywords: sendbee
|
|
11
|
-
Platform: UNKNOWN
|
|
9
|
+
Keywords: sendbee,api,python
|
|
12
10
|
Classifier: Intended Audience :: Developers
|
|
13
11
|
Classifier: Topic :: Software Development :: Build Tools
|
|
14
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -16,30 +14,28 @@ Classifier: Programming Language :: Python :: 3.6
|
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.7
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.6
|
|
19
22
|
Description-Content-Type: text/markdown
|
|
20
23
|
License-File: LICENSE
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
Requires-Dist: click>=7.0
|
|
25
|
+
Requires-Dist: requests>=2.20.0
|
|
26
|
+
Requires-Dist: dumpit>=0.5.0
|
|
27
|
+
Requires-Dist: aenum>=2.1.2
|
|
28
|
+
Requires-Dist: ujson<6,>=5.7.0
|
|
29
|
+
Requires-Dist: cryptography>=3.2
|
|
30
|
+
Requires-Dist: curlify>=2.2.1
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# AI Number Python API Client
|
|
32
37
|
|
|
33
38
|
[](https://badge.fury.io/py/sendbee-api)
|
|
34
|
-
[](https://travis-ci.org/sendbee/sendbee-python-api-client)
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-

|
|
38
|
-

|
|
39
|
-
|
|
40
|
-

|
|
41
|
-

|
|
42
|
-

|
|
43
39
|
|
|
44
40
|
## Table of contents
|
|
45
41
|
|
|
@@ -692,5 +688,3 @@ api = SendbeeApi(
|
|
|
692
688
|
Once you enabled the internal debug tool, every request to API will output various request and response data in standard output:
|
|
693
689
|
|
|
694
690
|

|
|
695
|
-
|
|
696
|
-
|
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
|
|
5
|
-
.' '. __
|
|
6
|
-
. . . (__\_
|
|
7
|
-
. . . -{{_(|8)
|
|
8
|
-
' . . ' ' . . ' (__/
|
|
9
|
-
|
|
10
|
-
```
|
|
1
|
+
# AI Number Python API Client
|
|
11
2
|
|
|
12
3
|
[](https://badge.fury.io/py/sendbee-api)
|
|
13
|
-
[](https://travis-ci.org/sendbee/sendbee-python-api-client)
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-

|
|
17
|
-

|
|
18
|
-
|
|
19
|
-

|
|
20
|
-

|
|
21
|
-

|
|
22
4
|
|
|
23
5
|
## Table of contents
|
|
24
6
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sendbee_api"
|
|
7
|
+
version = "1.7.2"
|
|
8
|
+
description = "Python client SDK for Sendbee Public API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.6"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Sendbee ltd", email = "info@sendbee.io" }]
|
|
13
|
+
keywords = ["sendbee", "api", "python"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Topic :: Software Development :: Build Tools",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3.6",
|
|
19
|
+
"Programming Language :: Python :: 3.7",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"click>=7.0",
|
|
29
|
+
"requests>=2.20.0",
|
|
30
|
+
"dumpit>=0.5.0",
|
|
31
|
+
"aenum>=2.1.2",
|
|
32
|
+
"ujson>=5.7.0,<6",
|
|
33
|
+
"cryptography>=3.2",
|
|
34
|
+
"curlify>=2.2.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest", "responses>=0.23"]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/sendbee/sendbee-python-client"
|
|
42
|
+
Source = "https://github.com/sendbee/sendbee-python-client"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["sendbee_api*"]
|
|
46
|
+
exclude = ["tests*"]
|
|
@@ -190,6 +190,10 @@ def bind_request(**request_data):
|
|
|
190
190
|
self.parameters[constants.RequestConst.QUERY]
|
|
191
191
|
)
|
|
192
192
|
self.debug.ok(constants.ResponseConst.RESPONSE_OBJECT, response)
|
|
193
|
+
self.debug.set_curl(
|
|
194
|
+
constants.DebugConst.CURL,
|
|
195
|
+
response.request
|
|
196
|
+
)
|
|
193
197
|
|
|
194
198
|
return response.status_code, response.headers, response.text
|
|
195
199
|
|
|
@@ -213,7 +217,13 @@ def bind_request(**request_data):
|
|
|
213
217
|
self.debug.ok('payload', self.parameters[
|
|
214
218
|
constants.RequestConst.QUERY
|
|
215
219
|
])
|
|
216
|
-
self.debug.ok(
|
|
220
|
+
self.debug.ok(
|
|
221
|
+
constants.ResponseConst.RESPONSE_OBJECT, response
|
|
222
|
+
)
|
|
223
|
+
self.debug.set_curl(
|
|
224
|
+
constants.DebugConst.CURL,
|
|
225
|
+
response.request
|
|
226
|
+
)
|
|
217
227
|
|
|
218
228
|
return response.status_code, response.headers, response.text
|
|
219
229
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
1
2
|
from collections import namedtuple
|
|
2
3
|
|
|
3
4
|
|
|
@@ -64,3 +65,23 @@ ResponseDataConst = namedtuple(
|
|
|
64
65
|
'ResponseDataConst', ['DATA', 'META']
|
|
65
66
|
)('data', 'meta')
|
|
66
67
|
|
|
68
|
+
|
|
69
|
+
class ConstMeta(ABCMeta):
|
|
70
|
+
def __setattr__(self, name, value):
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Const(metaclass=ConstMeta):
|
|
75
|
+
"""Helper class """
|
|
76
|
+
__slots__ = ()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DebugConst(Const):
|
|
80
|
+
PARAMETERS = 'parameters'
|
|
81
|
+
RESPONSE = 'response'
|
|
82
|
+
STATUS_CODE = 'status code'
|
|
83
|
+
FINAL_URL = 'final url'
|
|
84
|
+
HEADERS = 'headers'
|
|
85
|
+
METHOD = 'method'
|
|
86
|
+
QUERY_PARAMETERS = 'query parameters'
|
|
87
|
+
CURL = 'curl'
|
|
@@ -41,6 +41,8 @@ class SentMessage(Model):
|
|
|
41
41
|
|
|
42
42
|
_conversation_id = TextField(index='id', desc='Conversation UUID')
|
|
43
43
|
_message_id = TextField(index='message_id', desc='Message UUID')
|
|
44
|
+
_message_reference_id = TextField(index='message_reference_id',
|
|
45
|
+
desc='Message Reference UUID')
|
|
44
46
|
_status = TextField(index='status', desc='Message status')
|
|
45
47
|
|
|
46
48
|
|
|
@@ -21,6 +21,7 @@ class SendTemplateMessage(QueryParams):
|
|
|
21
21
|
prevent_bot_off = 'prevent_bot_off', 'Prevent turning-off chatbot'
|
|
22
22
|
agent_id = 'agent_id', 'Assigned agent for the conversation'
|
|
23
23
|
attachment = 'attachment', 'Attachment URL for media templates'
|
|
24
|
+
live_inbox = 'live_inbox', 'Trigger real-time events after sending'
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class SendMessage(QueryParams):
|
|
@@ -31,6 +32,7 @@ class SendMessage(QueryParams):
|
|
|
31
32
|
media_url = 'media_url', 'Media URL for media message'
|
|
32
33
|
prevent_bot_off = 'prevent_bot_off', 'Prevent turning-off chatbot'
|
|
33
34
|
agent_id = 'agent_id', 'Assigned agent for the conversation'
|
|
35
|
+
live_inbox = 'live_inbox', 'Trigger real-time events after sending'
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class ListConversations(QueryParams):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import
|
|
2
|
+
import curlify
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
STATUS_OK = 'ok'
|
|
@@ -25,6 +25,13 @@ class Debug:
|
|
|
25
25
|
def error(self, key, value):
|
|
26
26
|
self.record(key, value, STATUS_ERROR)
|
|
27
27
|
|
|
28
|
+
def set_curl(self, key, request):
|
|
29
|
+
if self.client.debug:
|
|
30
|
+
try:
|
|
31
|
+
self.ok(key, curlify.to_curl(request, compressed=False))
|
|
32
|
+
except UnicodeDecodeError:
|
|
33
|
+
self.ok(key, '__unable_to_decode__')
|
|
34
|
+
|
|
28
35
|
def record(self, key, value, status):
|
|
29
36
|
if self.client.debug:
|
|
30
37
|
self.recorded_data[key] = {'value': value, 'status': status}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 1.7.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sendbee_api
|
|
3
|
+
Version: 1.7.2
|
|
4
4
|
Summary: Python client SDK for Sendbee Public API
|
|
5
|
-
|
|
6
|
-
Author: Sendbee ltd
|
|
7
|
-
Author-email: info@sendbee.io
|
|
5
|
+
Author-email: Sendbee ltd <info@sendbee.io>
|
|
8
6
|
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sendbee/sendbee-python-client
|
|
9
8
|
Project-URL: Source, https://github.com/sendbee/sendbee-python-client
|
|
10
|
-
Keywords: sendbee
|
|
11
|
-
Platform: UNKNOWN
|
|
9
|
+
Keywords: sendbee,api,python
|
|
12
10
|
Classifier: Intended Audience :: Developers
|
|
13
11
|
Classifier: Topic :: Software Development :: Build Tools
|
|
14
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -16,30 +14,28 @@ Classifier: Programming Language :: Python :: 3.6
|
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.7
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.6
|
|
19
22
|
Description-Content-Type: text/markdown
|
|
20
23
|
License-File: LICENSE
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
Requires-Dist: click>=7.0
|
|
25
|
+
Requires-Dist: requests>=2.20.0
|
|
26
|
+
Requires-Dist: dumpit>=0.5.0
|
|
27
|
+
Requires-Dist: aenum>=2.1.2
|
|
28
|
+
Requires-Dist: ujson<6,>=5.7.0
|
|
29
|
+
Requires-Dist: cryptography>=3.2
|
|
30
|
+
Requires-Dist: curlify>=2.2.1
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# AI Number Python API Client
|
|
32
37
|
|
|
33
38
|
[](https://badge.fury.io/py/sendbee-api)
|
|
34
|
-
[](https://travis-ci.org/sendbee/sendbee-python-api-client)
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-

|
|
38
|
-

|
|
39
|
-
|
|
40
|
-

|
|
41
|
-

|
|
42
|
-

|
|
43
39
|
|
|
44
40
|
## Table of contents
|
|
45
41
|
|
|
@@ -692,5 +688,3 @@ api = SendbeeApi(
|
|
|
692
688
|
Once you enabled the internal debug tool, every request to API will output various request and response data in standard output:
|
|
693
689
|
|
|
694
690
|

|
|
695
|
-
|
|
696
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
|
-
|
|
3
|
+
pyproject.toml
|
|
4
4
|
sendbee_api/__init__.py
|
|
5
5
|
sendbee_api/auth.py
|
|
6
6
|
sendbee_api/bind.py
|
|
@@ -37,4 +37,13 @@ sendbee_api/rate_limit/query_params.py
|
|
|
37
37
|
sendbee_api/teams/__init__.py
|
|
38
38
|
sendbee_api/teams/client.py
|
|
39
39
|
sendbee_api/teams/models.py
|
|
40
|
-
sendbee_api/teams/query_params.py
|
|
40
|
+
sendbee_api/teams/query_params.py
|
|
41
|
+
tests/test_auth.py
|
|
42
|
+
tests/test_bind_request.py
|
|
43
|
+
tests/test_client.py
|
|
44
|
+
tests/test_endpoints_smoke.py
|
|
45
|
+
tests/test_fields.py
|
|
46
|
+
tests/test_formatter.py
|
|
47
|
+
tests/test_models.py
|
|
48
|
+
tests/test_query_params.py
|
|
49
|
+
tests/test_response.py
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tests for SendbeeAuth: HMAC token generation and verification."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hmac
|
|
5
|
+
import hashlib
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from sendbee_api.auth import SendbeeAuth
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_get_auth_token_returns_base64_encoded_timestamp_dot_hmac():
|
|
14
|
+
"""Token format is base64(<unix_ts>.<sha256_hmac_hex>)."""
|
|
15
|
+
auth = SendbeeAuth("secret")
|
|
16
|
+
|
|
17
|
+
token = auth.get_auth_token()
|
|
18
|
+
|
|
19
|
+
decoded = base64.b64decode(token).decode("utf-8")
|
|
20
|
+
timestamp, hex_digest = decoded.split(".")
|
|
21
|
+
assert timestamp.isdigit()
|
|
22
|
+
assert len(hex_digest) == 64
|
|
23
|
+
int(hex_digest, 16)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_auth_token_uses_hmac_sha256_of_timestamp_under_private_key(monkeypatch):
|
|
27
|
+
"""HMAC payload is the timestamp string, key is the private key."""
|
|
28
|
+
fixed_ts = 1700000000
|
|
29
|
+
secret = "secret-bytes"
|
|
30
|
+
|
|
31
|
+
class _FrozenDatetime(datetime):
|
|
32
|
+
@classmethod
|
|
33
|
+
def now(cls, tz=None):
|
|
34
|
+
return datetime.fromtimestamp(fixed_ts, tz=tz)
|
|
35
|
+
|
|
36
|
+
monkeypatch.setattr("sendbee_api.auth.datetime", _FrozenDatetime)
|
|
37
|
+
|
|
38
|
+
expected_hmac = hmac.new(
|
|
39
|
+
secret.encode("utf-8"),
|
|
40
|
+
str(fixed_ts).encode("utf-8"),
|
|
41
|
+
hashlib.sha256,
|
|
42
|
+
).hexdigest()
|
|
43
|
+
|
|
44
|
+
token = SendbeeAuth(secret).get_auth_token()
|
|
45
|
+
|
|
46
|
+
decoded = base64.b64decode(token).decode("utf-8")
|
|
47
|
+
timestamp, hex_digest = decoded.split(".")
|
|
48
|
+
assert timestamp == str(fixed_ts)
|
|
49
|
+
assert hex_digest == expected_hmac
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_get_auth_token_changes_when_timestamp_changes(monkeypatch):
|
|
53
|
+
"""Two tokens generated at different timestamps differ."""
|
|
54
|
+
secret = "secret"
|
|
55
|
+
timestamps = iter([1700000000, 1700000001])
|
|
56
|
+
|
|
57
|
+
class _CountingDatetime(datetime):
|
|
58
|
+
@classmethod
|
|
59
|
+
def now(cls, tz=None):
|
|
60
|
+
return datetime.fromtimestamp(next(timestamps), tz=tz)
|
|
61
|
+
|
|
62
|
+
monkeypatch.setattr("sendbee_api.auth.datetime", _CountingDatetime)
|
|
63
|
+
|
|
64
|
+
token_a = SendbeeAuth(secret).get_auth_token()
|
|
65
|
+
token_b = SendbeeAuth(secret).get_auth_token()
|
|
66
|
+
|
|
67
|
+
assert token_a != token_b
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_check_auth_token_returns_true_for_self_generated_token():
|
|
71
|
+
"""A token produced by get_auth_token verifies under the same secret."""
|
|
72
|
+
auth = SendbeeAuth("secret")
|
|
73
|
+
|
|
74
|
+
token = auth.get_auth_token()
|
|
75
|
+
|
|
76
|
+
assert auth.check_auth_token(token) is True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_check_auth_token_returns_false_when_hmac_tampered():
|
|
80
|
+
"""A token whose HMAC has been altered fails verification."""
|
|
81
|
+
auth = SendbeeAuth("secret")
|
|
82
|
+
token = auth.get_auth_token()
|
|
83
|
+
decoded = base64.b64decode(token).decode("utf-8")
|
|
84
|
+
timestamp, hex_digest = decoded.split(".")
|
|
85
|
+
tampered_hex = ("0" if hex_digest[0] != "0" else "1") + hex_digest[1:]
|
|
86
|
+
tampered = base64.b64encode(
|
|
87
|
+
f"{timestamp}.{tampered_hex}".encode("utf-8")
|
|
88
|
+
).decode("utf-8")
|
|
89
|
+
|
|
90
|
+
assert auth.check_auth_token(tampered) is False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_check_auth_token_returns_false_when_secret_differs():
|
|
94
|
+
"""A token signed with secret A cannot be verified with secret B."""
|
|
95
|
+
token = SendbeeAuth("secret-a").get_auth_token()
|
|
96
|
+
other = SendbeeAuth("secret-b")
|
|
97
|
+
|
|
98
|
+
assert other.check_auth_token(token) is False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_constructor_accepts_str_and_bytes_keys():
|
|
102
|
+
"""Both str and bytes private keys produce identical tokens."""
|
|
103
|
+
fixed_ts = b"1700000000"
|
|
104
|
+
expected = hmac.new(b"secret", fixed_ts, hashlib.sha256).hexdigest()
|
|
105
|
+
|
|
106
|
+
str_auth = SendbeeAuth("secret")
|
|
107
|
+
bytes_auth = SendbeeAuth(b"secret")
|
|
108
|
+
|
|
109
|
+
assert str_auth._get_encrypted_key(fixed_ts) == expected
|
|
110
|
+
assert bytes_auth._get_encrypted_key(fixed_ts) == expected
|