Skip to content

Commit f65afe5

Browse files
authored
4.x gnp (#300)
* add camara auth module * start adding gnp sim swap * adding new gnp packages * update camara auth * add network packages * finish adding network apis and prepare for release * adjust verify v2 channel timeout and prepare for release
1 parent 280ebf2 commit f65afe5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+758
-23
lines changed

http_client/CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 1.4.0
2+
- Add new `oauth2` logic for calling APIs that require Oauth
3+
14
# 1.3.1
25
- Update minimum dependency version
36

http_client/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[project]
22
name = "vonage-http-client"
3-
version = "1.3.1"
3+
version = "1.4.0"
44
description = "An HTTP client for making requests to Vonage APIs."
55
readme = "README.md"
66
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
77
requires-python = ">=3.8"
88
dependencies = [
9-
"vonage-utils>=1.1.1",
9+
"vonage-utils>=1.1.2",
1010
"vonage-jwt>=1.1.1",
1111
"requests>=2.27.0",
1212
"typing-extensions>=4.9.0",

http_client/src/vonage_http_client/http_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,12 @@ def post(
130130
host: str,
131131
request_path: str = '',
132132
params: dict = None,
133-
auth_type: Literal['jwt', 'basic', 'body', 'signature'] = 'jwt',
134-
sent_data_type: Literal['json', 'data'] = 'json',
133+
auth_type: Literal['jwt', 'basic', 'body', 'signature', 'oauth2'] = 'jwt',
134+
sent_data_type: Literal['json', 'form', 'query-params'] = 'json',
135+
token: Optional[str] = None,
135136
) -> Union[dict, None]:
136137
return self.make_request(
137-
'POST', host, request_path, params, auth_type, sent_data_type
138+
'POST', host, request_path, params, auth_type, sent_data_type, token
138139
)
139140

140141
def get(
@@ -192,8 +193,9 @@ def make_request(
192193
host: str,
193194
request_path: str = '',
194195
params: Optional[dict] = None,
195-
auth_type: Literal['jwt', 'basic', 'body', 'signature'] = 'jwt',
196+
auth_type: Literal['jwt', 'basic', 'body', 'signature', 'oauth2'] = 'jwt',
196197
sent_data_type: Literal['json', 'form', 'query_params'] = 'json',
198+
token: Optional[str] = None,
197199
):
198200
url = f'https://{host}{request_path}'
199201
logger.debug(
@@ -206,6 +208,8 @@ def make_request(
206208
elif auth_type == 'body':
207209
params['api_key'] = self._auth.api_key
208210
params['api_secret'] = self._auth.api_secret
211+
elif auth_type == 'oauth2':
212+
self._headers['Authorization'] = f'Bearer {token}'
209213
elif auth_type == 'signature':
210214
params['api_key'] = self._auth.api_key
211215
params['sig'] = self._auth.sign_params(params)

network_auth/BUILD

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
resource(name='pyproject', source='pyproject.toml')
2+
file(name='readme', source='README.md')
3+
4+
files(sources=['tests/data/*'])
5+
6+
python_distribution(
7+
name='vonage-network-auth',
8+
dependencies=[
9+
':pyproject',
10+
':readme',
11+
'network_auth/src/vonage_network_auth',
12+
],
13+
provides=python_artifact(),
14+
generate_setup=False,
15+
repositories=['@pypi'],
16+
)

network_auth/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 0.1.0b0
2+
- Initial upload

network_auth/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Vonage Network API Authentication Client
2+
3+
This package (`vonage-network-auth`) provides a client for authenticating Network APIs that require Oauth2 authentcation. Using it, it is possible to generate authenticated JWTs for use with GNP APIs, e.g. Sim Swap, Number Verification.
4+
5+
This package is intended to be used as part of an SDK, accessing required methods through the SDK instead of directly. Thus, it doesn't require manual installation or configuration unless you're using this package independently of an SDK.
6+
7+
For full API documentation, refer to the [Vonage developer documentation](https://developer.vonage.com).
8+
9+
Please note this package is in beta.
10+
11+
## Installation
12+
13+
Install from the Python Package Index with pip:
14+
15+
```bash
16+
pip install vonage-network-auth
17+
```
18+
19+
## Usage
20+
21+
### Create a `NetworkAuth` Object
22+
23+
```python
24+
from vonage_network_auth import NetworkAuth
25+
from vonage_http_client import HttpClient, Auth
26+
27+
network_auth = NetworkAuth(HttpClient(Auth(application_id='application-id', private_key='private-key')))
28+
```
29+
30+
### Generate an Authenticated Access Token
31+
32+
```python
33+
token = network_auth.get_oauth2_user_token(
34+
number='447700900000', scope='dpv:FraudPreventionAndDetection#check-sim-swap'
35+
)
36+
```

network_auth/pyproject.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[project]
2+
name = "vonage-network-auth"
3+
version = "0.1.0b0"
4+
description = "Package for working with Network APIs that require Oauth2 in Python."
5+
readme = "README.md"
6+
authors = [{ name = "Vonage", email = "devrel@vonage.com" }]
7+
requires-python = ">=3.8"
8+
dependencies = ["vonage-http-client>=1.4.0", "vonage-utils>=1.1.2"]
9+
classifiers = [
10+
"Programming Language :: Python",
11+
"Programming Language :: Python :: 3",
12+
"Programming Language :: Python :: 3.8",
13+
"Programming Language :: Python :: 3.9",
14+
"Programming Language :: Python :: 3.10",
15+
"Programming Language :: Python :: 3.11",
16+
"Programming Language :: Python :: 3.12",
17+
"License :: OSI Approved :: Apache Software License",
18+
]
19+
20+
[project.urls]
21+
Homepage = "https://github.com/Vonage/vonage-python-sdk"
22+
23+
[build-system]
24+
requires = ["setuptools>=61.0", "wheel"]
25+
build-backend = "setuptools.build_meta"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python_sources()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .network_auth import NetworkAuth
2+
from .responses import OidcResponse, TokenResponse
3+
4+
__all__ = ['NetworkAuth', 'OidcResponse', 'TokenResponse']
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from pydantic import validate_call
2+
from vonage_http_client.http_client import HttpClient
3+
4+
from .responses import OidcResponse, TokenResponse
5+
6+
7+
class NetworkAuth:
8+
"""Class containing methods for authenticating Network APIs following Camara standards."""
9+
10+
def __init__(self, http_client: HttpClient):
11+
self._http_client = http_client
12+
self._host = 'api-eu.vonage.com'
13+
self._auth_type = 'jwt'
14+
self._sent_data_type = 'form'
15+
16+
@property
17+
def http_client(self) -> HttpClient:
18+
"""The HTTP client used to make requests to the Network Auth API.
19+
20+
Returns:
21+
HttpClient: The HTTP client used to make requests to the Network Auth API.
22+
"""
23+
return self._http_client
24+
25+
@validate_call
26+
def get_oauth2_user_token(self, number: str, scope: str) -> str:
27+
"""Get an OAuth2 user token for a given number and scope.
28+
29+
Args:
30+
number (str): The phone number to authenticate.
31+
scope (str): The scope of the token.
32+
33+
Returns:
34+
str: The OAuth2 user token.
35+
"""
36+
oidc_response = self.make_oidc_request(number, scope)
37+
token_response = self.request_access_token(oidc_response.auth_req_id)
38+
return token_response.access_token
39+
40+
@validate_call
41+
def make_oidc_request(self, number: str, scope: str) -> OidcResponse:
42+
"""Make an OIDC request to authenticate a user.
43+
44+
Args:
45+
number (str): The phone number to authenticate.
46+
scope (str): The scope of the token.
47+
48+
Returns:
49+
OidcResponse: A response containing the authentication request ID.
50+
"""
51+
number = self._ensure_plus_prefix(number)
52+
params = {'login_hint': number, 'scope': scope}
53+
54+
response = self._http_client.post(
55+
self._host,
56+
'/oauth2/bc-authorize',
57+
params,
58+
self._auth_type,
59+
self._sent_data_type,
60+
)
61+
return OidcResponse(**response)
62+
63+
@validate_call
64+
def request_access_token(
65+
self, auth_req_id: str, grant_type: str = 'urn:openid:params:grant-type:ciba'
66+
) -> TokenResponse:
67+
"""Request a Camara access token using an authentication request ID given as a response to
68+
an OIDC request."""
69+
params = {'auth_req_id': auth_req_id, 'grant_type': grant_type}
70+
71+
response = self._http_client.post(
72+
self._host,
73+
'/oauth2/token',
74+
params,
75+
self._auth_type,
76+
self._sent_data_type,
77+
)
78+
return TokenResponse(**response)
79+
80+
def _ensure_plus_prefix(self, number: str) -> str:
81+
"""Ensure that the number has a plus prefix.
82+
83+
Args:
84+
number (str): The phone number to check.
85+
86+
Returns:
87+
str: The phone number with a plus prefix.
88+
"""
89+
if number.startswith('+'):
90+
return number
91+
return f'+{number}'

0 commit comments

Comments
 (0)