Skip to content

Commit 12a728e

Browse files
authored
Resolve VCSWP-21848 (#46)
* Fix python sdk bugs as mentioned in ticket * Push up changes to init file after generation * Add import freeclimb lines to the two new tests * Move manual tests outside manual folder for testing purposes * Move the tests back into the manual folder * Add changelog update to Python SDK PR * Edit latest changelog entry
1 parent 2108817 commit 12a728e

File tree

11 files changed

+183
-6
lines changed

11 files changed

+183
-6
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
99

1010
None
1111

12+
<a name="4.5.2"></a>
13+
14+
## [4.5.2] - 2024-04-05
15+
16+
### Changed
17+
18+
- Fixed Signature Information and Request Verifier class bugs
19+
1220
<a name="4.5.1"></a>
1321

1422
## [4.5.1] - 2023-09-01

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FreeClimb is a cloud-based application programming interface (API) that puts the
44
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
55

66
- API version: 1.0.0
7-
- Package version: 4.5.1
7+
- Package version: 4.5.2
88
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
99
For more information, please visit [https://www.freeclimb.com/support/](https://www.freeclimb.com/support/)
1010

freeclimb/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
"""
1212

1313

14-
__version__ = "4.5.1"
14+
__version__ = "4.5.2"
1515

1616
# import ApiClient
1717
from freeclimb.api_client import ApiClient
1818

1919
# import Configuration
2020
from freeclimb.configuration import Configuration
2121

22+
# Utils
23+
from freeclimb.utils.request_verifier import RequestVerifier
24+
2225
# import exceptions
2326
from freeclimb.exceptions import OpenApiException
2427
from freeclimb.exceptions import ApiAttributeError

freeclimb/api_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(self, configuration=None, header_name=None, header_value=None,
7777
self.default_headers[header_name] = header_value
7878
self.cookie = cookie
7979
# Set default User-Agent.
80-
self.user_agent = 'OpenAPI-Generator/4.5.1/python'
80+
self.user_agent = 'OpenAPI-Generator/4.5.2/python'
8181

8282
def __enter__(self):
8383
return self

freeclimb/configuration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ def to_debug_report(self):
405405
"OS: {env}\n"\
406406
"Python Version: {pyversion}\n"\
407407
"Version of the API: 1.0.0\n"\
408-
"SDK Package Version: 4.5.1".\
408+
"SDK Package Version: 4.5.2".\
409409
format(env=sys.platform, pyversion=sys.version)
410410

411411
def get_host_settings(self):

freeclimb/utils/__init__.py

Whitespace-only changes.

freeclimb/utils/signature_information.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, request_header:str):
1616
def is_request_time_valid(self, tolerance:int) -> bool:
1717
current_time = self.get_current_unix_time()
1818
time_calculation:int = self.request_timestamp + tolerance
19-
return (time_calculation) < current_time
19+
return current_time < (time_calculation)
2020

2121
def is_signature_safe(self, requestBody:str, signingSecret:str) -> bool:
2222
hashValue = self.__compute_hash(requestBody, signingSecret)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from setuptools import setup, find_packages # noqa: H301
1313

1414
NAME = "FreeClimb"
15-
VERSION = "4.5.1"
15+
VERSION = "4.5.2"
1616
# To install the library, run the following
1717
#
1818
# python setup.py install

test/manual/__init__.py

Whitespace-only changes.

test/manual/test_request_verifier.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import sys
2+
import time
3+
import unittest
4+
5+
from freeclimb.utils.request_verifier import RequestVerifier
6+
7+
class TestRequestVerifier(unittest.TestCase):
8+
"""RequestVerifier unit test stubs"""
9+
10+
def setUp(self):
11+
self.request_verifier = RequestVerifier()
12+
13+
def tearDown(self):
14+
pass
15+
16+
def test_check_request_body(self):
17+
request_body = ""
18+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
19+
tolerance = 5 * 60 * 1000
20+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
21+
with self.assertRaises(Exception) as exc:
22+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
23+
self.assertEqual(str(exc.exception), "Request Body cannot be empty or null")
24+
25+
def test_check_request_header_no_signatures(self):
26+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
27+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
28+
tolerance = 5 * 60 * 1000
29+
request_header = "t=1679944186,"
30+
with self.assertRaises(Exception) as exc:
31+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
32+
self.assertEqual(str(exc.exception), "Error with request header, signatures are not present")
33+
34+
def test_check_request_header_no_timestamp(self):
35+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
36+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
37+
tolerance = 5 * 60 * 1000
38+
request_header = "v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
39+
with self.assertRaises(Exception) as exc:
40+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
41+
self.assertEqual(str(exc.exception), "Error with request header, timestamp is not present")
42+
43+
def test_check_request_header_empty_request_header(self):
44+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
45+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
46+
tolerance = 5 * 60 * 1000
47+
request_header = ""
48+
with self.assertRaises(Exception) as exc:
49+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
50+
self.assertEqual(str(exc.exception), "Error with request header, Request header is empty")
51+
52+
def test_check_signing_secret(self):
53+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
54+
signing_secret = ""
55+
tolerance = 5 * 60 * 1000
56+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
57+
with self.assertRaises(Exception) as exc:
58+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
59+
self.assertEqual(str(exc.exception), "Signing secret cannot be empty or null")
60+
61+
def test_check_tolerance_max_int(self):
62+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
63+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
64+
tolerance = sys.maxsize
65+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
66+
with self.assertRaises(Exception) as exc:
67+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
68+
self.assertEqual(str(exc.exception), "Tolerance value must be a positive integer")
69+
70+
def test_check_tolerance_zero_value(self):
71+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
72+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
73+
tolerance = 0
74+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
75+
with self.assertRaises(Exception) as exc:
76+
self.request_verifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
77+
self.assertEqual(str(exc.exception), "Tolerance value must be a positive integer")
78+
79+
def test_check_tolerance_negative_value(self):
80+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
81+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
82+
tolerance = -5
83+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
84+
with self.assertRaises(Exception) as exc:
85+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
86+
self.assertEqual(str(exc.exception), "Tolerance value must be a positive integer")
87+
88+
def test_verify_tolerance(self):
89+
current_time = int(time.time())
90+
request_header_time = current_time - (6*60*1000)
91+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
92+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
93+
tolerance = 5 * 60 * 1000
94+
request_header = "t=" + str(request_header_time) + ",v1=1d798c86e977ff734dec3a8b8d67fe8621dcc1df46ef4212e0bfe2e122b01bfd,v1=78e363373f8a9f6fddc2e3ca3a1ed9dc94efa44d378363adf52434e78f32d8fb"
95+
with self.assertRaises(Exception) as exc:
96+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
97+
self.assertEqual(str(exc.exception), "Request time exceeded tolerance threshold. Request: " + str(request_header_time)
98+
+ ", CurrentTime: " + str(current_time) + ", tolerance: " + str(tolerance))
99+
100+
def test_verify_signature(self):
101+
current_time = int(time.time())
102+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
103+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7794"
104+
tolerance = 5 * 60 * 1000
105+
request_header = "t=" + str(current_time) + ",v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
106+
with self.assertRaises(Exception) as exc:
107+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
108+
self.assertEqual(str(exc.exception), "Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret")
109+
110+
def test_verify_request_signature(self):
111+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
112+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
113+
tolerance = 5 * 60 * 1000
114+
request_header = "t=3795106129,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=96b769b34426fa69117dd6d1bda2c432beb2cf4c63dd2de495fca0c37dde82b2"
115+
raised = False
116+
try:
117+
RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
118+
except:
119+
raised = True
120+
self.assertFalse(raised, 'Exception has been raised')
121+
122+
123+
124+
125+
if __name__ == '__main__':
126+
unittest.main()

0 commit comments

Comments
 (0)