Skip to content

Commit 1806bc0

Browse files
authored
Add jwt signing (#287)
* add method to check jwt signatures to voice api * Bump version: 3.10.0 → 3.11.0
1 parent a8b0716 commit 1806bc0

File tree

8 files changed

+41
-11
lines changed

8 files changed

+41
-11
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.10.0
2+
current_version = 3.11.0
33
commit = True
44
tag = False
55

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 3.11.0
2+
- Add method to check JWT signatures of Voice API webhooks: `vonage.Voice.verify_signature`
3+
14
# 3.10.0
25
- Indicating support for Python 3.12
36

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,18 @@ client.voice.send_dtmf(response['uuid'], digits='1234')
347347
response = client.get_recording(RECORDING_URL)
348348
```
349349

350+
### Verify the Signature of a Webhook Sent by Vonage
351+
352+
If signed webhooks are enabled (the default), Vonage will sign webhooks with the signature secret found in the [API Settings](https://dashboard.nexmo.com/settings) section of the Vonage Developer Dashboard.
353+
354+
```python
355+
if client.voice.verify_signature('JWT_RECEIVED_FROM_VONAGE', 'MY_VONAGE_SIGNATURE_SECRET'):
356+
print('Signature is valid!')
357+
else:
358+
print('Signature is invalid!')
359+
```
360+
361+
350362
## NCCO Builder
351363

352364
The SDK contains a builder to help you create Call Control Objects (NCCOs) for use with the Vonage Voice API.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-e .
2-
pytest==7.2.0
2+
pytest==7.4.2
33
responses==0.22.0
44
coverage
55
pydantic>=1.10,==1.*

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name="vonage",
12-
version="3.10.0",
12+
version="3.11.0",
1313
description="Vonage Server SDK for Python",
1414
long_description=long_description,
1515
long_description_content_type="text/markdown",
@@ -21,7 +21,7 @@
2121
package_dir={"": "src"},
2222
platforms=["any"],
2323
install_requires=[
24-
"vonage-jwt>=1.0.0",
24+
"vonage-jwt>=1.1.0",
2525
"requests>=2.4.2",
2626
"pytz>=2018.5",
2727
"Deprecated",

src/vonage/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .client import *
22
from .ncco_builder.ncco import *
33

4-
__version__ = "3.10.0"
4+
__version__ = "3.11.0"

src/vonage/voice.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from urllib.parse import urlparse
2+
from vonage_jwt.verify_jwt import verify_signature
23

34

45
class Voice:
@@ -94,3 +95,6 @@ def get_recording(self, url):
9495
headers = self._client.headers
9596
headers['Authorization'] = self._client._create_jwt_auth_string()
9697
return self._client.parse(hostname, self._client.session.get(url, headers=headers))
98+
99+
def verify_signature(self, token: str, signature: str) -> bool:
100+
return verify_signature(token, signature)

tests/test_voice.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import os.path
22
import time
3-
43
import jwt
4+
from unittest.mock import patch
55

6-
import vonage
7-
from vonage import Ncco
6+
from vonage import Client, Voice, Ncco
87
from util import *
98

109

@@ -149,7 +148,7 @@ def test_user_provided_authorization(dummy_data):
149148
stub(responses.GET, "https://api.nexmo.com/v1/calls/xx-xx-xx-xx")
150149

151150
application_id = "different-application-id"
152-
client = vonage.Client(application_id=application_id, private_key=dummy_data.private_key)
151+
client = Client(application_id=application_id, private_key=dummy_data.private_key)
153152

154153
nbf = int(time.time())
155154
exp = nbf + 3600
@@ -172,13 +171,13 @@ def test_authorization_with_private_key_path(dummy_data):
172171

173172
private_key = os.path.join(os.path.dirname(__file__), "data/private_key.txt")
174173

175-
client = vonage.Client(
174+
client = Client(
176175
key=dummy_data.api_key,
177176
secret=dummy_data.api_secret,
178177
application_id=dummy_data.application_id,
179178
private_key=private_key,
180179
)
181-
voice = vonage.Voice(client)
180+
voice = Voice(client)
182181
voice.get_call("xx-xx-xx-xx")
183182

184183
token = jwt.decode(
@@ -212,3 +211,15 @@ def test_get_recording(voice, dummy_data):
212211
bytes,
213212
)
214213
assert request_user_agent() == dummy_data.user_agent
214+
215+
216+
def test_verify_jwt_signature(voice: Voice):
217+
with patch('vonage.Voice.verify_signature') as mocked_verify_signature:
218+
mocked_verify_signature.return_value = True
219+
assert voice.verify_signature('valid_token', 'valid_signature')
220+
221+
222+
def test_verify_jwt_invalid_signature(voice: Voice):
223+
with patch('vonage.Voice.verify_signature') as mocked_verify_signature:
224+
mocked_verify_signature.return_value = False
225+
assert voice.verify_signature('token', 'invalid_signature') is False

0 commit comments

Comments
 (0)