Skip to content

Commit

Permalink
Add jwt signing (#287)
Browse files Browse the repository at this point in the history
* add method to check jwt signatures to voice api

* Bump version: 3.10.0 → 3.11.0
  • Loading branch information
maxkahan committed Oct 19, 2023
1 parent a8b0716 commit 1806bc0
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.10.0
current_version = 3.11.0
commit = True
tag = False

Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 3.11.0
- Add method to check JWT signatures of Voice API webhooks: `vonage.Voice.verify_signature`

# 3.10.0
- Indicating support for Python 3.12

Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,18 @@ client.voice.send_dtmf(response['uuid'], digits='1234')
response = client.get_recording(RECORDING_URL)
```

### Verify the Signature of a Webhook Sent by Vonage

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.

```python
if client.voice.verify_signature('JWT_RECEIVED_FROM_VONAGE', 'MY_VONAGE_SIGNATURE_SECRET'):
print('Signature is valid!')
else:
print('Signature is invalid!')
```


## NCCO Builder

The SDK contains a builder to help you create Call Control Objects (NCCOs) for use with the Vonage Voice API.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-e .
pytest==7.2.0
pytest==7.4.2
responses==0.22.0
coverage
pydantic>=1.10,==1.*
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

setup(
name="vonage",
version="3.10.0",
version="3.11.0",
description="Vonage Server SDK for Python",
long_description=long_description,
long_description_content_type="text/markdown",
Expand All @@ -21,7 +21,7 @@
package_dir={"": "src"},
platforms=["any"],
install_requires=[
"vonage-jwt>=1.0.0",
"vonage-jwt>=1.1.0",
"requests>=2.4.2",
"pytz>=2018.5",
"Deprecated",
Expand Down
2 changes: 1 addition & 1 deletion src/vonage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .client import *
from .ncco_builder.ncco import *

__version__ = "3.10.0"
__version__ = "3.11.0"
4 changes: 4 additions & 0 deletions src/vonage/voice.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from urllib.parse import urlparse
from vonage_jwt.verify_jwt import verify_signature


class Voice:
Expand Down Expand Up @@ -94,3 +95,6 @@ def get_recording(self, url):
headers = self._client.headers
headers['Authorization'] = self._client._create_jwt_auth_string()
return self._client.parse(hostname, self._client.session.get(url, headers=headers))

def verify_signature(self, token: str, signature: str) -> bool:
return verify_signature(token, signature)
23 changes: 17 additions & 6 deletions tests/test_voice.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import os.path
import time

import jwt
from unittest.mock import patch

import vonage
from vonage import Ncco
from vonage import Client, Voice, Ncco
from util import *


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

application_id = "different-application-id"
client = vonage.Client(application_id=application_id, private_key=dummy_data.private_key)
client = Client(application_id=application_id, private_key=dummy_data.private_key)

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

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

client = vonage.Client(
client = Client(
key=dummy_data.api_key,
secret=dummy_data.api_secret,
application_id=dummy_data.application_id,
private_key=private_key,
)
voice = vonage.Voice(client)
voice = Voice(client)
voice.get_call("xx-xx-xx-xx")

token = jwt.decode(
Expand Down Expand Up @@ -212,3 +211,15 @@ def test_get_recording(voice, dummy_data):
bytes,
)
assert request_user_agent() == dummy_data.user_agent


def test_verify_jwt_signature(voice: Voice):
with patch('vonage.Voice.verify_signature') as mocked_verify_signature:
mocked_verify_signature.return_value = True
assert voice.verify_signature('valid_token', 'valid_signature')


def test_verify_jwt_invalid_signature(voice: Voice):
with patch('vonage.Voice.verify_signature') as mocked_verify_signature:
mocked_verify_signature.return_value = False
assert voice.verify_signature('token', 'invalid_signature') is False

0 comments on commit 1806bc0

Please sign in to comment.