vobiz-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. vobiz/__init__.py +4 -0
  2. vobiz/base.py +237 -0
  3. vobiz/exceptions.py +34 -0
  4. vobiz/resources/__init__.py +12 -0
  5. vobiz/resources/accounts.py +59 -0
  6. vobiz/resources/applications.py +138 -0
  7. vobiz/resources/calls_vobiz.py +206 -0
  8. vobiz/resources/cdrs.py +46 -0
  9. vobiz/resources/credentials.py +104 -0
  10. vobiz/resources/endpoints.py +101 -0
  11. vobiz/resources/ip_access_control_lists.py +100 -0
  12. vobiz/resources/numbers.py +134 -0
  13. vobiz/resources/origination_uris.py +109 -0
  14. vobiz/resources/recordings.py +91 -0
  15. vobiz/resources/sip_trunks.py +99 -0
  16. vobiz/resources/subaccounts.py +101 -0
  17. vobiz/rest/__init__.py +2 -0
  18. vobiz/rest/client.py +277 -0
  19. vobiz/utils/__init__.py +72 -0
  20. vobiz/utils/interactive.py +50 -0
  21. vobiz/utils/jwt.py +97 -0
  22. vobiz/utils/location.py +6 -0
  23. vobiz/utils/signature_v3.py +111 -0
  24. vobiz/utils/template.py +50 -0
  25. vobiz/utils/validators.py +280 -0
  26. vobiz/version.py +2 -0
  27. vobiz/xml/ConferenceElement.py +485 -0
  28. vobiz/xml/DTMFElement.py +41 -0
  29. vobiz/xml/DialElement.py +371 -0
  30. vobiz/xml/MultiPartyCallElement.py +711 -0
  31. vobiz/xml/ResponseElement.py +414 -0
  32. vobiz/xml/VobizXMLElement.py +48 -0
  33. vobiz/xml/__init__.py +31 -0
  34. vobiz/xml/breakElement.py +62 -0
  35. vobiz/xml/contElement.py +190 -0
  36. vobiz/xml/emphasisElement.py +174 -0
  37. vobiz/xml/getDigitsElement.py +294 -0
  38. vobiz/xml/getInputElement.py +369 -0
  39. vobiz/xml/hangupElement.py +57 -0
  40. vobiz/xml/langElement.py +197 -0
  41. vobiz/xml/messageElement.py +115 -0
  42. vobiz/xml/numberElement.py +77 -0
  43. vobiz/xml/pElement.py +164 -0
  44. vobiz/xml/phonemeElement.py +62 -0
  45. vobiz/xml/playElement.py +41 -0
  46. vobiz/xml/preAnswerElement.py +148 -0
  47. vobiz/xml/prosodyElement.py +227 -0
  48. vobiz/xml/recordElement.py +337 -0
  49. vobiz/xml/redirectElement.py +42 -0
  50. vobiz/xml/sElement.py +154 -0
  51. vobiz/xml/sayAsElement.py +61 -0
  52. vobiz/xml/speakElement.py +249 -0
  53. vobiz/xml/streamElement.py +123 -0
  54. vobiz/xml/subElement.py +43 -0
  55. vobiz/xml/userElement.py +79 -0
  56. vobiz/xml/wElement.py +144 -0
  57. vobiz/xml/waitElement.py +93 -0
  58. vobiz/xml/xmlUtils.py +6 -0
  59. vobiz_python-0.1.0.dist-info/METADATA +640 -0
  60. vobiz_python-0.1.0.dist-info/RECORD +63 -0
  61. vobiz_python-0.1.0.dist-info/WHEEL +5 -0
  62. vobiz_python-0.1.0.dist-info/licenses/LICENSE.txt +19 -0
  63. vobiz_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,99 @@
1
+ from typing import Any, Dict, Optional
2
+
3
+ VOBIZ_API_V1 = "https://api.vobiz.ai/api/v1"
4
+
5
+
6
+ class SipTrunks:
7
+ """
8
+ Vobiz SIP Trunks resource.
9
+
10
+ Uses the `/api/v1/account/{account_id}/trunks/...` paths described in
11
+ the Vobiz SIP trunks documentation.
12
+ """
13
+
14
+ def __init__(self, client):
15
+ self.client = client
16
+
17
+ @property
18
+ def _account_id(self) -> str:
19
+ # For Vobiz, we treat the RestClient auth_id as the account_id
20
+ return self.client.auth_id
21
+
22
+ def create(
23
+ self,
24
+ name: str,
25
+ inbound_uri: Optional[str] = None,
26
+ outbound_uri: Optional[str] = None,
27
+ **extra: Any,
28
+ ):
29
+ """
30
+ POST /api/v1/account/{account_id}/trunks
31
+ """
32
+ url = f"{VOBIZ_API_V1}/account/{self._account_id}/trunks"
33
+ body: Dict[str, Any] = {"name": name}
34
+ if inbound_uri is not None:
35
+ body["inbound_uri"] = inbound_uri
36
+ if outbound_uri is not None:
37
+ body["outbound_uri"] = outbound_uri
38
+ body.update(extra)
39
+
40
+ resp = self.client.session.post(
41
+ url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
42
+ )
43
+ return self.client.process_response("POST", resp)
44
+
45
+ def list(
46
+ self,
47
+ page: Optional[int] = None,
48
+ size: Optional[int] = None,
49
+ **filters: Any,
50
+ ):
51
+ """
52
+ GET /api/v1/account/{account_id}/trunks
53
+ """
54
+ url = f"{VOBIZ_API_V1}/account/{self._account_id}/trunks"
55
+ params: Dict[str, Any] = {}
56
+ if page is not None:
57
+ params["page"] = page
58
+ if size is not None:
59
+ params["size"] = size
60
+ params.update(filters)
61
+
62
+ resp = self.client.session.get(
63
+ url, params=params, timeout=self.client.timeout, proxies=self.client.proxies
64
+ )
65
+ return self.client.process_response("GET", resp)
66
+
67
+ def get(self, trunk_id: str):
68
+ """
69
+ GET /api/v1/account/{account_id}/trunks/{trunk_id}
70
+ """
71
+ url = f"{VOBIZ_API_V1}/account/{self._account_id}/trunks/{trunk_id}"
72
+ resp = self.client.session.get(
73
+ url, timeout=self.client.timeout, proxies=self.client.proxies
74
+ )
75
+ return self.client.process_response("GET", resp)
76
+
77
+ def update(self, trunk_id: str, **params: Any):
78
+ """
79
+ PUT /api/v1/account/{account_id}/trunks/{trunk_id}
80
+ """
81
+ url = f"{VOBIZ_API_V1}/account/{self._account_id}/trunks/{trunk_id}"
82
+ body: Dict[str, Any] = dict(params)
83
+ resp = self.client.session.put(
84
+ url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
85
+ )
86
+ return self.client.process_response("PUT", resp)
87
+
88
+ def delete(self, trunk_id: str):
89
+ """
90
+ DELETE /api/v1/account/{account_id}/trunks/{trunk_id}
91
+
92
+ Permanently delete a SIP trunk and its associated resources.
93
+ """
94
+ url = f"{VOBIZ_API_V1}/account/{self._account_id}/trunks/{trunk_id}"
95
+ resp = self.client.session.delete(
96
+ url, timeout=self.client.timeout, proxies=self.client.proxies
97
+ )
98
+ return self.client.process_response("DELETE", resp)
99
+
@@ -0,0 +1,101 @@
1
+ from typing import Any, Dict, Optional
2
+
3
+ VOBIZ_API_V1 = "https://api.vobiz.ai/api/v1"
4
+
5
+
6
+ class Subaccounts:
7
+ def __init__(self, client):
8
+ self.client = client
9
+
10
+ @property
11
+ def _account_id(self) -> str:
12
+ # For Vobiz, we treat the RestClient auth_id as the account_id
13
+ return self.client.auth_id
14
+
15
+ def create(
16
+ self,
17
+ name: str,
18
+ email: str,
19
+ rate_limit: int,
20
+ permissions: Any,
21
+ password: str,
22
+ phone: Optional[str] = None,
23
+ description: Optional[str] = None,
24
+ enabled: bool = True,
25
+ ):
26
+ """
27
+ POST /api/v1/accounts/{account_id}/sub-accounts/
28
+ """
29
+ url = f"{VOBIZ_API_V1}/accounts/{self._account_id}/sub-accounts/"
30
+ body: Dict[str, Any] = {
31
+ "name": name,
32
+ "email": email,
33
+ "rate_limit": rate_limit,
34
+ "permissions": permissions,
35
+ "password": password,
36
+ "enabled": enabled,
37
+ }
38
+ if phone is not None:
39
+ body["phone"] = phone
40
+ if description is not None:
41
+ body["description"] = description
42
+
43
+ resp = self.client.session.post(
44
+ url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
45
+ )
46
+ return self.client.process_response("POST", resp)
47
+
48
+ def list(
49
+ self,
50
+ page: Optional[int] = None,
51
+ size: Optional[int] = None,
52
+ active_only: Optional[bool] = None,
53
+ ):
54
+ """
55
+ GET /api/v1/accounts/{account_id}/sub-accounts/
56
+ """
57
+ url = f"{VOBIZ_API_V1}/accounts/{self._account_id}/sub-accounts/"
58
+ params: Dict[str, Any] = {}
59
+ if page is not None:
60
+ params["page"] = page
61
+ if size is not None:
62
+ params["size"] = size
63
+ if active_only is not None:
64
+ params["active_only"] = active_only
65
+
66
+ resp = self.client.session.get(
67
+ url, params=params, timeout=self.client.timeout, proxies=self.client.proxies
68
+ )
69
+ return self.client.process_response("GET", resp)
70
+
71
+ def get(self, sub_account_id: str):
72
+ """
73
+ GET /api/v1/accounts/{account_id}/sub-accounts/{sub_account_id}
74
+ """
75
+ url = f"{VOBIZ_API_V1}/accounts/{self._account_id}/sub-accounts/{sub_account_id}"
76
+ resp = self.client.session.get(
77
+ url, timeout=self.client.timeout, proxies=self.client.proxies
78
+ )
79
+ return self.client.process_response("GET", resp)
80
+
81
+ def update(self, sub_account_id: str, **params: Any):
82
+ """
83
+ PUT /api/v1/accounts/{account_id}/sub-accounts/{sub_account_id}
84
+ """
85
+ url = f"{VOBIZ_API_V1}/accounts/{self._account_id}/sub-accounts/{sub_account_id}"
86
+ body: Dict[str, Any] = dict(params)
87
+ resp = self.client.session.put(
88
+ url, json=body, timeout=self.client.timeout, proxies=self.client.proxies
89
+ )
90
+ return self.client.process_response("PUT", resp)
91
+
92
+ def delete(self, sub_account_id: str):
93
+ """
94
+ DELETE /api/v1/accounts/{account_id}/sub-accounts/{sub_account_id}
95
+ """
96
+ url = f"{VOBIZ_API_V1}/accounts/{self._account_id}/sub-accounts/{sub_account_id}"
97
+ resp = self.client.session.delete(
98
+ url, timeout=self.client.timeout, proxies=self.client.proxies
99
+ )
100
+ return self.client.process_response("DELETE", resp)
101
+
vobiz/rest/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .client import Client
vobiz/rest/client.py ADDED
@@ -0,0 +1,277 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Core client, used for all API requests.
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ from collections import namedtuple
9
+
10
+ from dotenv import load_dotenv
11
+ load_dotenv()
12
+
13
+ from vobiz.base import ResponseObject
14
+ from vobiz.exceptions import (
15
+ AuthenticationError,
16
+ InvalidRequestError,
17
+ VobizRestError,
18
+ VobizServerError,
19
+ GeoPermissionError,
20
+ ResourceNotFoundError,
21
+ ValidationError,
22
+ ForbiddenError,
23
+ )
24
+ from vobiz.resources import (
25
+ Calls,
26
+ Accounts,
27
+ Subaccounts,
28
+ Applications,
29
+ Recordings,
30
+ CDRs,
31
+ PhoneNumbers,
32
+ Endpoints,
33
+ SipTrunks,
34
+ Credentials,
35
+ IpAccessControlLists,
36
+ OriginationUris,
37
+ )
38
+ from vobiz.version import __version__
39
+ from requests import Request, Session
40
+
41
+ AuthenticationCredentials = namedtuple("AuthenticationCredentials", "auth_id auth_token")
42
+
43
+ VOBIZ_API = "https://api.vobiz.ai/api"
44
+ VOBIZ_API_BASE_URI = "/".join([VOBIZ_API, "v1/Account"])
45
+
46
+ GEO_PERMISSION_ENDPOINTS = ["/Call/"]
47
+
48
+
49
+ def get_user_agent():
50
+ return "vobiz-python/%s (Python: %s)" % (__version__, platform.python_version())
51
+
52
+
53
+ def fetch_credentials(auth_id, auth_token):
54
+ """Fetch credentials from arguments"""
55
+
56
+ if not (auth_id and auth_token):
57
+ try:
58
+ auth_id = os.environ["VOBIZ_AUTH_ID"]
59
+ auth_token = os.environ["VOBIZ_AUTH_TOKEN"]
60
+ except KeyError:
61
+ raise AuthenticationError(
62
+ "The Vobiz Python SDK could not find your auth credentials."
63
+ )
64
+
65
+ if not isinstance(auth_id, str) or not auth_id:
66
+ raise AuthenticationError("Invalid auth_id supplied: %s" % auth_id)
67
+
68
+ if not isinstance(auth_token, str) or not auth_token:
69
+ raise AuthenticationError("Invalid auth_token supplied.")
70
+
71
+ return AuthenticationCredentials(auth_id=auth_id, auth_token=auth_token)
72
+
73
+
74
+ class Client(object):
75
+ def __init__(self, auth_id=None, auth_token=None, proxies=None, timeout=5):
76
+ """
77
+ The Vobiz API client.
78
+
79
+ Deals with all the API requests to be made.
80
+ """
81
+
82
+ self.base_uri = VOBIZ_API_BASE_URI
83
+ self.session = Session()
84
+ self.session.headers.update(
85
+ {
86
+ "User-Agent": get_user_agent(),
87
+ "Content-Type": "application/json",
88
+ "Accept": "application/json",
89
+ }
90
+ )
91
+ creds = fetch_credentials(auth_id, auth_token)
92
+ self.auth_id = creds.auth_id
93
+ self.auth_token = creds.auth_token
94
+ self.session.headers.update(
95
+ {"X-Auth-ID": self.auth_id, "X-Auth-Token": self.auth_token}
96
+ )
97
+ self.multipart_session = Session()
98
+ self.multipart_session.headers.update(
99
+ {
100
+ "User-Agent": get_user_agent(),
101
+ "Cache-Control": "no-cache",
102
+ }
103
+ )
104
+ self.multipart_session.headers.update(
105
+ {"X-Auth-ID": self.auth_id, "X-Auth-Token": self.auth_token}
106
+ )
107
+ self.proxies = proxies
108
+ self.timeout = timeout
109
+ self.calls = Calls(self)
110
+ self.accounts = Accounts(self)
111
+ self.subaccounts = Subaccounts(self)
112
+ self.applications = Applications(self)
113
+ self.recordings = Recordings(self)
114
+ self.cdrs = CDRs(self)
115
+ self.phone_numbers = PhoneNumbers(self)
116
+ self.endpoints = Endpoints(self)
117
+ self.sip_trunks = SipTrunks(self)
118
+ self.credentials = Credentials(self)
119
+ self.ip_access_control_lists = IpAccessControlLists(self)
120
+ self.origination_uris = OriginationUris(self)
121
+
122
+
123
+ def __enter__(self):
124
+ return self
125
+
126
+ def __exit__(self, exc_type, exc_val, exc_tb):
127
+ self.session.close()
128
+ self.multipart_session.close()
129
+
130
+ def process_response(self,
131
+ method,
132
+ response,
133
+ response_type=None,
134
+ objects_type=None):
135
+ """Processes the API response based on the status codes and method used
136
+ to access the API
137
+ """
138
+ try:
139
+ response_json = response.json(
140
+ object_hook=lambda x: ResponseObject(x) if isinstance(x, dict) else x)
141
+ if response_type:
142
+ r = response_type(self, response_json.__dict__)
143
+ response_json = r
144
+
145
+ if 'objects' in response_json and objects_type:
146
+ response_json.objects = [
147
+ objects_type(self, obj.__dict__)
148
+ for obj in response_json.objects
149
+ ]
150
+ except ValueError:
151
+ response_json = None
152
+
153
+ if response.status_code == 400:
154
+ if response_json is not None and 'error' in response_json:
155
+ raise ValidationError(response_json.error)
156
+ raise ValidationError(
157
+ 'A parameter is missing or is invalid while accessing resource'
158
+ 'at: {url}'.format(url=response.url))
159
+
160
+ if response.status_code == 401:
161
+ if response_json and 'error' in response_json:
162
+ raise AuthenticationError(response_json.error)
163
+ raise AuthenticationError(
164
+ 'Failed to authenticate while accessing resource at: '
165
+ '{url}'.format(url=response.url))
166
+
167
+ if response.status_code == 403:
168
+ error = ForbiddenError
169
+ if method == "POST" and any(response.url.endswith(endpoint) for endpoint in GEO_PERMISSION_ENDPOINTS):
170
+ error = GeoPermissionError
171
+ if response_json and 'error' in response_json:
172
+ raise error(response_json.error)
173
+ raise error(
174
+ 'Request Failed : '
175
+ '{url}'.format(url=response.url))
176
+
177
+ if response.status_code == 404:
178
+ if response_json and 'error' in response_json:
179
+ raise ResourceNotFoundError(response_json.error)
180
+ raise ResourceNotFoundError(
181
+ 'Resource not found at: {url}'.format(url=response.url))
182
+
183
+ if response.status_code == 405:
184
+ if response_json and 'error' in response_json:
185
+ raise InvalidRequestError(response_json.error)
186
+ raise InvalidRequestError(
187
+ 'HTTP method "{method}" not allowed to access resource at: '
188
+ '{url}'.format(method=method, url=response.url))
189
+
190
+ if response.status_code == 409:
191
+ if response_json and 'error' in response_json:
192
+ raise InvalidRequestError(response_json.error)
193
+ raise InvalidRequestError(
194
+ 'Conflict: '
195
+ '{url}'.format(url=response.url))
196
+
197
+ if response.status_code == 422:
198
+ if response_json and 'error' in response_json:
199
+ raise InvalidRequestError(response_json.error)
200
+ raise InvalidRequestError(
201
+ 'Unprocessable Entity: '
202
+ '{url}'.format(url=response.url))
203
+
204
+ if response.status_code == 500:
205
+ if response_json and 'error' in response_json:
206
+ raise VobizServerError(response_json.error)
207
+ raise VobizServerError(
208
+ 'A server error occurred while accessing resource at: '
209
+ '{url}'.format(url=response.url))
210
+
211
+ if method == 'DELETE':
212
+ if response.status_code not in [200, 202, 204]:
213
+ raise VobizRestError('Resource at {url} could not be '
214
+ 'deleted'.format(url=response.url))
215
+
216
+ elif response.status_code not in [200, 201, 202, 204, 206, 207]:
217
+ raise VobizRestError(
218
+ 'Received status code {status_code} for the HTTP method '
219
+ '"{method}"'.format(
220
+ status_code=response.status_code, method=method))
221
+
222
+ return response_json
223
+
224
+ def create_request(self, method, path=None, data=None, **kwargs):
225
+ path = path or []
226
+ url = '/'.join([self.base_uri, self.auth_id] + list([str(p) for p in path])) + '/'
227
+ req = Request(
228
+ method,
229
+ url,
230
+ **({'params': data} if method == 'GET' else {'json': data}),
231
+ )
232
+ return self.session.prepare_request(req)
233
+
234
+ def create_multipart_request(self,
235
+ method,
236
+ path=None,
237
+ data=None,
238
+ files=None):
239
+ path = path or []
240
+
241
+ data_args = {}
242
+ if method == 'GET':
243
+ data_args['params'] = data
244
+ else:
245
+ data_args['data'] = data
246
+ if files:
247
+ data_args['files'] = files
248
+ url = '/'.join([self.base_uri, self.auth_id] + list([str(p) for p in path])) + '/'
249
+ req = Request(method, url, **data_args)
250
+ return self.multipart_session.prepare_request(req)
251
+
252
+ def send_request(self, request, **kwargs):
253
+ if 'session' in kwargs:
254
+ session = kwargs['session']
255
+ del kwargs['session']
256
+ else:
257
+ session = self.session
258
+ return session.send(
259
+ request, proxies=self.proxies, timeout=self.timeout, **kwargs)
260
+
261
+ def request(self,
262
+ method,
263
+ path=None,
264
+ data=None,
265
+ response_type=None,
266
+ objects_type=None,
267
+ files=None,
268
+ **kwargs):
269
+ if files is not None:
270
+ req = self.create_multipart_request(method, path, data, files)
271
+ session = self.multipart_session
272
+ else:
273
+ req = self.create_request(method, path, data)
274
+ session = self.session
275
+ kwargs['session'] = session
276
+ res = self.send_request(req, **kwargs)
277
+ return self.process_response(method, res, response_type, objects_type)
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+ import inspect
3
+ import re
4
+ from datetime import datetime
5
+
6
+ from hmac import new as hnew
7
+ from hashlib import sha256
8
+ from urllib.parse import urlparse, urlunparse
9
+ from base64 import encodebytes as base64_encode
10
+ from inspect import getfullargspec as getargspec
11
+
12
+ from .signature_v3 import validate_v3_signature
13
+
14
+
15
+ def validate_signature(uri, nonce, signature, auth_token=''):
16
+ """
17
+ Validates requests made by Vobiz to your servers.
18
+
19
+ :param uri: Your server URL
20
+ :param nonce: X-Vobiz-Signature-V2-Nonce
21
+ :param signature: X-Vobiz-Signature-V2 header
22
+ :param auth_token: Vobiz Auth token
23
+ :return: True if the request matches signature, False otherwise
24
+ """
25
+
26
+ auth_token = bytes(auth_token.encode('utf-8'))
27
+ nonce = bytes(nonce.encode('utf-8'))
28
+ signature = bytes(signature.encode('utf-8'))
29
+
30
+ parsed_uri = urlparse(uri.encode('utf-8'))
31
+ base_url = urlunparse((parsed_uri.scheme.decode('utf-8'),
32
+ parsed_uri.netloc.decode('utf-8'),
33
+ parsed_uri.path.decode('utf-8'), '', '',
34
+ '')).encode('utf-8')
35
+
36
+ return base64_encode(hnew(auth_token, base_url + nonce, sha256)
37
+ .digest()).strip() == signature
38
+
39
+
40
+ def is_valid_time_comparison(time):
41
+ if isinstance(time, datetime):
42
+ return True
43
+ return False
44
+
45
+
46
+ def is_valid_subaccount(subaccount):
47
+ subaccount_string = str(subaccount)
48
+ if len(subaccount_string) == 20 and subaccount_string[:2] == 'SA':
49
+ return True
50
+ return False
51
+
52
+
53
+ def is_valid_mainaccount(mainaccount):
54
+ mainaccount_string = str(mainaccount)
55
+ if len(mainaccount_string) == 20 and mainaccount_string[:2] == 'MA':
56
+ return True
57
+ return False
58
+
59
+
60
+ def to_param_dict(func, vals, exclude_none=True, func_args_check=True):
61
+ args = getargspec(func)[0]
62
+ arg_names = list(args)
63
+ # The bit of regex magic below is for arguments that are keywords in
64
+ # Python, like from. These can't be used directly, so our convention is to
65
+ # add "_" suffixes to them. This strips them out.
66
+ pd = {
67
+ re.sub(r'^(.*)_+$', r'\1', key): value
68
+ for key, value in vals.items()
69
+ if key != 'self' and (key in arg_names or func_args_check == False) and (
70
+ value is not None or exclude_none is False)
71
+ }
72
+ return pd
@@ -0,0 +1,50 @@
1
+ class Header:
2
+ def __init__(self, type=None, text=None, media=None):
3
+ self.type = type
4
+ self.text = text
5
+ self.media = media
6
+
7
+
8
+ class Body:
9
+ def __init__(self, text=None):
10
+ self.text = text
11
+
12
+
13
+ class Footer:
14
+ def __init__(self, text=None):
15
+ self.text = text
16
+
17
+
18
+ class Row:
19
+ def __init__(self, id=None, title=None, description=None):
20
+ self.id = id
21
+ self.title = title
22
+ self.description = description
23
+
24
+
25
+ class Section:
26
+ def __init__(self, title=None, rows=None):
27
+ self.title = title
28
+ self.rows = rows if rows is not None else []
29
+
30
+
31
+ class Btn:
32
+ def __init__(self, id=None, title=None, cta_url=None):
33
+ self.id = id
34
+ self.title = title
35
+ self.cta_url = cta_url
36
+
37
+
38
+ class Action:
39
+ def __init__(self, buttons=None, sections=None):
40
+ self.buttons = buttons if buttons is not None else []
41
+ self.sections = sections if sections is not None else []
42
+
43
+
44
+ class Interactive:
45
+ def __init__(self, type=None, header=None, body=None, footer=None, action=None):
46
+ self.type = type
47
+ self.header = header
48
+ self.body = body
49
+ self.footer = footer
50
+ self.action = action
vobiz/utils/jwt.py ADDED
@@ -0,0 +1,97 @@
1
+ from __future__ import absolute_import
2
+ import jwt
3
+ import time
4
+
5
+ from vobiz.utils.validators import *
6
+
7
+
8
+ class AccessToken:
9
+ """
10
+ Vobiz JWT AccessToken for SIP endpoint authentication.
11
+
12
+ Used to generate short-lived JWT tokens for WebRTC/SIP clients.
13
+ Not used for REST API authentication (which uses X-Auth-ID / X-Auth-Token headers).
14
+ """
15
+ auth_id = ''
16
+ username = ''
17
+ valid_from = 0
18
+ lifetime = 86400
19
+ key = ''
20
+ grants = {}
21
+ uid = 0
22
+
23
+ @validate_args(
24
+ auth_id=[is_account_id()],
25
+ auth_token=[optional(of_type(str))],
26
+ username=[all_of(
27
+ of_type(str),
28
+ check(lambda username: len(username) > 0, 'empty username')
29
+ )],
30
+ valid_from=[optional(of_type(int))],
31
+ lifetime=[
32
+ optional(
33
+ all_of(
34
+ of_type(int),
35
+ check(lambda lifetime: 180 <= lifetime <= 86400,
36
+ '180 < lifetime <= 86400')))
37
+ ],
38
+ valid_till=[optional(of_type(int))],
39
+ )
40
+ def __init__(self,
41
+ auth_id,
42
+ auth_token,
43
+ username,
44
+ valid_from=None,
45
+ lifetime=None,
46
+ valid_till=None,
47
+ uid=None):
48
+ self.auth_id = auth_id
49
+ self.username = username
50
+ if valid_from:
51
+ self.valid_from = int(valid_from)
52
+ else:
53
+ self.valid_from = int(time.time())
54
+ if lifetime:
55
+ self.lifetime = int(lifetime)
56
+ if valid_till is not None:
57
+ raise ValidationError("use either lifetime or valid_till")
58
+ elif valid_till:
59
+ self.lifetime = valid_till - self.valid_from
60
+ if self.lifetime < 0:
61
+ raise ValidationError(
62
+ "validity expires %s seconds before it starts" %
63
+ self.lifetime)
64
+ if self.lifetime < 180 or self.lifetime > 86400:
65
+ raise ValidationError(
66
+ "validity of %s seconds is out of permitted range [180, 86400]" %
67
+ self.lifetime)
68
+
69
+ self.key = auth_token
70
+
71
+ if uid:
72
+ self.uid = uid
73
+ else:
74
+ self.uid = "%s-%s" % (username, time.time())
75
+
76
+ @validate_args(
77
+ incoming=[optional(of_type_exact(bool))],
78
+ outgoing=[optional(of_type_exact(bool))],
79
+ )
80
+ def add_voice_grants(self, incoming=False, outgoing=False):
81
+ self.grants['voice'] = {
82
+ 'incoming_allow': incoming,
83
+ 'outgoing_allow': outgoing
84
+ }
85
+
86
+ def to_jwt(self):
87
+ headers = {'typ': 'JWT', 'cty': 'vobiz;v=1'}
88
+ algorithm = 'HS256'
89
+ claims = {
90
+ 'jti': self.uid,
91
+ 'iss': self.auth_id,
92
+ 'sub': self.username,
93
+ 'nbf': self.valid_from,
94
+ 'exp': self.valid_from + self.lifetime,
95
+ 'grants': self.grants
96
+ }
97
+ return jwt.encode(claims, self.key, algorithm=algorithm, headers=headers)