Skip to content

Commit 52e94c4

Browse files
committed
fix mocks, signatures and sig auth, add tests
1 parent 691835e commit 52e94c4

File tree

7 files changed

+97
-84
lines changed

7 files changed

+97
-84
lines changed

http_client/src/vonage_http_client/auth.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from base64 import b64encode
2-
from typing import Literal, Optional
31
import hashlib
42
import hmac
3+
from base64 import b64encode
4+
55
from time import time
6+
from typing import Literal, Optional
67

78
from pydantic import validate_call
89
from vonage_jwt.jwt import JwtClient
@@ -77,16 +78,16 @@ def create_basic_auth_string(self):
7778
)
7879
return f'Basic {hash}'
7980

80-
def sign_params(self, params: dict) -> dict:
81-
"""
82-
Signs the provided message parameters using the signature secret provided to the `Auth` class.
83-
If no signature secret is provided, the message parameters are signed using a simple MD5 hash.
81+
def sign_params(self, params: dict) -> str:
82+
"""Signs the provided message parameters using the signature secret provided to the `Auth`
83+
class. If no signature secret is provided, the message parameters are signed using a simple
84+
MD5 hash.
8485
8586
Args:
8687
params (dict): The message parameters to be signed.
8788
8889
Returns:
89-
dict: The signed message parameters.
90+
str: A hexadecimal digest of the signed message parameters.
9091
"""
9192

9293
hasher = hmac.new(
@@ -96,6 +97,7 @@ def sign_params(self, params: dict) -> dict:
9697

9798
if not params.get('timestamp'):
9899
params['timestamp'] = int(time())
100+
print(params['timestamp'])
99101

100102
for key in sorted(params):
101103
value = params[key]
@@ -105,14 +107,23 @@ def sign_params(self, params: dict) -> dict:
105107

106108
hasher.update(f'&{key}={value}'.encode('utf-8'))
107109

108-
if self._signature_method is None:
109-
hasher.update(self._signature_secret.encode())
110110
return hasher.hexdigest()
111111

112112
@validate_call
113113
def check_signature(self, params: dict) -> bool:
114+
"""
115+
Checks the signature hash of the given parameters.
116+
117+
Args:
118+
params (dict): The parameters to check the signature for.
119+
This should include the `sig` parameter which contains the
120+
signature hash of the other parameters.
121+
122+
Returns:
123+
bool: True if the signature is valid, False otherwise.
124+
"""
114125
signature = params.pop('sig', '').lower()
115-
return hmac.compare_digest(signature, self._signature_secret(params))
126+
return hmac.compare_digest(signature, self.sign_params(params))
116127

117128
def _validate_input_combinations(
118129
self, api_key, api_secret, application_id, private_key, signature_secret

http_client/src/vonage_http_client/http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def make_request(
140140
with self._session.request(
141141
request_type,
142142
url,
143-
params=params,
143+
data=params,
144144
headers=self._headers,
145145
timeout=self._timeout,
146146
) as response:

http_client/tests/test_auth.py

Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
import hashlib
12
from os.path import dirname, join
23
from unittest.mock import patch
3-
import hashlib
4-
import hmac
54

65
from pydantic import ValidationError
76
from pytest import raises
@@ -36,6 +35,8 @@ def test_create_auth_class_and_get_objects():
3635
assert auth.api_key == api_key
3736
assert auth.api_secret == api_secret
3837
assert type(auth._jwt_client) == JwtClient
38+
assert auth._signature_secret == signature_secret
39+
assert auth._signature_method == hashlib.sha256
3940

4041

4142
def test_create_new_auth_invalid_type():
@@ -63,6 +64,10 @@ def test_auth_init_with_invalid_combinations():
6364
Auth(api_secret=api_secret, application_id=application_id)
6465
with raises(InvalidAuthError):
6566
Auth(api_secret=api_secret, private_key=private_key)
67+
with raises(InvalidAuthError):
68+
Auth(application_id=application_id, signature_secret=signature_secret)
69+
with raises(InvalidAuthError):
70+
Auth(private_key=private_key, signature_secret=signature_secret)
6671

6772

6873
def test_auth_init_with_valid_api_key_and_api_secret():
@@ -110,96 +115,64 @@ def test_create_basic_auth_string():
110115
assert auth.create_basic_auth_string() == 'Basic cXdlcmFzZGY6MTIzNHF3ZXJhc2Rmenhjdg=='
111116

112117

113-
def test_auth_init_with_valid_combinations():
114-
api_key = 'qwerasdf'
115-
api_secret = '1234qwerasdfzxcv'
116-
application_id = 'asdfzxcv'
117-
private_key = 'dummy_private_key'
118-
signature_secret = 'signature_secret'
119-
signature_method = 'sha256'
120-
118+
def test_sign_params():
121119
auth = Auth(
122120
api_key=api_key,
123-
api_secret=api_secret,
124-
application_id=application_id,
125-
private_key=private_key,
126121
signature_secret=signature_secret,
127122
signature_method=signature_method,
128123
)
129124

130-
assert auth._api_key == api_key
131-
assert auth._api_secret == api_secret
132-
assert auth._jwt_client.application_id == application_id
133-
assert auth._jwt_client.private_key == private_key
134-
assert auth._signature_secret == signature_secret
135-
assert auth._signature_method == hashlib.sha256
125+
params = {'param1': 'value1', 'param2': 'value2', 'timestamp': 1234567890}
136126

127+
signed_params_hash = auth.sign_params(params)
137128

138-
def test_auth_init_with_invalid_combinations():
139-
api_key = 'qwerasdf'
140-
api_secret = '1234qwerasdfzxcv'
141-
application_id = 'asdfzxcv'
142-
private_key = 'dummy_private_key'
143-
signature_secret = 'signature_secret'
144-
signature_method = 'invalid_method'
145-
146-
with patch('vonage_http_client.auth.hashlib') as mock_hashlib:
147-
mock_hashlib.sha256.side_effect = AttributeError
148-
149-
auth = Auth(
150-
api_key=api_key,
151-
api_secret=api_secret,
152-
application_id=application_id,
153-
private_key=private_key,
154-
signature_secret=signature_secret,
155-
signature_method=signature_method,
156-
)
157-
158-
assert auth._api_key == api_key
159-
assert auth._api_secret == api_secret
160-
assert auth._jwt_client is None
161-
assert auth._signature_secret == signature_secret
162-
assert auth._signature_method is None
129+
assert (
130+
signed_params_hash
131+
== '280c4320703dbc98bfa22db676655ed2acfbfe8792b062ff7622e67f1183c287'
132+
)
163133

164134

165-
def test_sign_params():
166-
auth = Auth(signature_secret='signature_secret', signature_method='sha256')
135+
def test_sign_params_default_sig_method():
136+
auth = Auth(api_key=api_key, signature_secret=signature_secret)
167137

168138
params = {'param1': 'value1', 'param2': 'value2', 'timestamp': 1234567890}
169139

170-
signed_params = auth.sign_params(params)
140+
signed_params_hash = auth.sign_params(params)
171141

172-
assert signed_params == 'asdf'
142+
assert signed_params_hash == '724c2bf6ca423c36e20631b11d1c5753'
173143

174144

175-
def test_sign_params_default_sig_method():
176-
auth = Auth()
145+
def test_sign_params_with_special_characters():
146+
auth = Auth(api_key=api_key, signature_secret=signature_secret)
177147

178-
params = {'param1': 'value1', 'param2': 'value2', 'timestamp': 1234567890}
148+
params = {'param1': 'value&1', 'param2': 'value=2', 'timestamp': 1234567890}
179149

180150
signed_params = auth.sign_params(params)
181151

182-
assert signed_params == 'asdf'
152+
assert signed_params == '2bbf0abafb2c55e5af6231513896a2ac'
183153

184154

185-
def test_sign_params_with_special_characters():
186-
auth = Auth(signature_secret='signature_secret', signature_method='sha1')
155+
@patch('vonage_http_client.auth.time', return_value=12345)
156+
def test_sign_params_with_dynamic_timestamp(mock_time):
157+
auth = Auth(api_key=api_key, signature_secret=signature_secret)
187158

188-
params = {'param1': 'value&1', 'param2': 'value=2', 'timestamp': 1234567890}
159+
params = {'param1': 'value1', 'param2': 'value2'}
189160

190161
signed_params = auth.sign_params(params)
191162

192-
assert signed_params == 'asdf'
163+
assert signed_params == 'bc7e95bb4e341090b3a202a2885903a5'
193164

194165

195-
# def test_check_signature_with_valid_signature():
196-
# auth = Auth(signature_secret='signature_secret')
197-
# params = {'param1': 'value1', 'param2': 'value2', 'sig': 'valid_signature'}
198-
# expected_signature = hmac.new(
199-
# b'signature_secret', b'param1value1param2value2', hashlib.sha256
200-
# ).hexdigest()
166+
def test_check_signature_with_valid_signature():
167+
auth = Auth(api_key=api_key, signature_secret=signature_secret)
168+
params = {
169+
'param1': 'value1',
170+
'param2': 'value2',
171+
'sig': 'valid_signature',
172+
'timestamp': 1234567890,
173+
}
201174

202-
# assert auth.check_signature(params) == True
175+
assert auth.check_signature(params) == True
203176

204177

205178
# def test_check_signature_with_invalid_signature():

http_client/tests/test_http_client.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import responses
55
from pytest import raises
66
from requests import Response
7+
from responses import matchers
78
from vonage_http_client.auth import Auth
89
from vonage_http_client.errors import (
910
AuthenticationError,
@@ -97,19 +98,33 @@ def test_make_post_request():
9798

9899
@responses.activate
99100
def test_make_post_request_with_signature():
101+
params = {
102+
'test': 'post request',
103+
'testing': 'http client',
104+
'timestamp': '1234567890',
105+
}
106+
100107
build_response(
101-
path, 'POST', 'https://example.com/post_signed_params', 'example_post.json'
108+
path,
109+
'POST',
110+
'https://example.com/post_signed_params',
111+
'example_post.json',
112+
match=[
113+
matchers.urlencoded_params_matcher(
114+
{
115+
**params,
116+
'api_key': 'asdfzxcv',
117+
'sig': '237b06fd1f994a9ec2f3283a4a0239f35b56d64639d6485b45cffedcb385b033',
118+
}
119+
)
120+
],
102121
)
103122
client = HttpClient(
104123
Auth(
105124
api_key='asdfzxcv', signature_secret='qwerasdfzxcv', signature_method='sha256'
106125
),
107126
http_client_options={'api_host': 'example.com'},
108127
)
109-
params = {
110-
'test': 'post request',
111-
'testing': 'http client',
112-
}
113128

114129
res = client.post(
115130
host='example.com',
@@ -118,8 +133,6 @@ def test_make_post_request_with_signature():
118133
auth_type='signature',
119134
)
120135
assert res['hello'] == 'world!'
121-
print(responses.calls[0].request.url)
122-
assert responses.calls[0].request.body == params
123136

124137

125138
@responses.activate

pants.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ filter = [
3131
'vonage/src',
3232
'http_client/src',
3333
'number_insight_v2/src',
34+
'sms/src',
3435
'utils/src',
3536
'testutils',
3637
]

sms/src/vonage_sms/sms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import BaseModel, Field, field_validator, validate_call
66
from vonage_http_client.http_client import HttpClient
77

8-
from .errors import SmsError, PartialFailureError
8+
from .errors import PartialFailureError, SmsError
99

1010

1111
class SmsMessage(BaseModel):

testutils/testutils.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ def _load_mock_data(caller_file_path: str, mock_path: str):
1010
return file.read()
1111

1212

13+
def _filter_none_values(data: dict) -> dict:
14+
return {k: v for (k, v) in data.items() if v is not None}
15+
16+
1317
@validate_call
1418
def build_response(
1519
file_path: str,
@@ -18,7 +22,18 @@ def build_response(
1822
mock_path: str = None,
1923
status_code: int = 200,
2024
content_type: str = 'application/json',
25+
match: list = None,
2126
):
22-
print('file_path', file_path)
2327
body = _load_mock_data(file_path, mock_path) if mock_path else None
24-
responses.add(method, url, body=body, status=status_code, content_type=content_type)
28+
responses.add(
29+
**_filter_none_values(
30+
{
31+
'method': method,
32+
'url': url,
33+
'body': body,
34+
'status': status_code,
35+
'content_type': content_type,
36+
'match': match,
37+
}
38+
)
39+
)

0 commit comments

Comments
 (0)