Skip to content

Commit 15e15c0

Browse files
committed
twilio python changes for orgs api uptake
1 parent fac26ee commit 15e15c0

File tree

16 files changed

+202
-63
lines changed

16 files changed

+202
-63
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from twilio.authStrategy.authType import AuthType
2+
from enum import Enum
3+
from abc import abstractmethod
4+
5+
6+
class AuthStrategy(object):
7+
def __init__(self, auth_type: AuthType):
8+
self._auth_type = auth_type
9+
10+
@property
11+
def auth_type(self) -> AuthType:
12+
return self._auth_type
13+
14+
def get_auth_string(self) -> str:
15+
"""Return the authentication string."""
16+
pass
17+
18+
@abstractmethod
19+
def requires_authentication(self) -> bool:
20+
"""Return True if authentication is required, else False."""
21+
pass

twilio/authStrategy/authType.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from enum import Enum
2+
3+
class AuthType(Enum):
4+
TOKEN = 'token'
5+
NO_AUTH = 'noauth'
6+
BASIC = 'basic'
7+
API_KEY = 'api_key'
8+
CLIENT_CREDENTIALS = 'client_credentials'
9+
10+
def __str__(self):
11+
return self.value
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import base64
2+
from enum import Enum
3+
4+
5+
class BasicAuthStrategy(AuthStrategy):
6+
def __init__(self, username: str, password: str):
7+
super().__init__(AuthType.BASIC)
8+
self.username = username
9+
self.password = password
10+
11+
def get_auth_string(self) -> str:
12+
credentials = f"{self.username}:{self.password}"
13+
encoded = base64.b64encode(credentials.encode('ascii')).decode('ascii')
14+
return f"Basic {encoded}"
15+
16+
def requires_authentication(self) -> bool:
17+
return True
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from auth_type import AuthType
2+
3+
class NoAuthStrategy(AuthStrategy):
4+
def __init__(self):
5+
super().__init__(AuthType.NO_AUTH)
6+
7+
def get_auth_string(self) -> str:
8+
return ""
9+
10+
def requires_authentication(self) -> bool:
11+
return False
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import jwt
2+
import threading
3+
from datetime import datetime, timedelta
4+
5+
from twilio.authStrategy.authType import AuthType
6+
from twilio.authStrategy.authStrategy import AuthStrategy
7+
from twilio.http.token_manager import TokenManager
8+
9+
10+
class TokenAuthStrategy(AuthStrategy):
11+
def __init__(self, token_manager: TokenManager):
12+
super().__init__(AuthType.TOKEN)
13+
self.token_manager = token_manager
14+
self.token = None
15+
self.lock = threading.Lock()
16+
17+
def get_auth_string(self) -> str:
18+
return f"Bearer {self.token}"
19+
20+
def requires_authentication(self) -> bool:
21+
return True
22+
23+
def fetch_token(self):
24+
if self.token is None or self.token == "" or self.is_token_expired(self.token):
25+
with self.lock:
26+
if self.token is None or self.token == "" or self.is_token_expired(self.token):
27+
self.token = self.token_manager.fetch_access_token()
28+
29+
def is_token_expired(self, token):
30+
decoded_jwt = jwt.decode(token, options={"verify_signature": True})
31+
expires_at = decoded_jwt.get("exp")
32+
# Add a buffer of 30 seconds
33+
buffer_seconds = 30
34+
buffer_expires_at = expires_at - buffer_seconds
35+
return buffer_expires_at < datetime.datetime.now().timestamp()

twilio/base/client_base.py

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from urllib.parse import urlparse, urlunparse
55

66
from twilio import __version__
7-
from twilio.base.exceptions import TwilioException
87
from twilio.http import HttpClient
98
from twilio.http.http_client import TwilioHttpClient
109
from twilio.http.response import Response
10+
from twilio.authStrategy.authType import AuthType
11+
from twilio.credential.credentialProvider import CredentialProvider
1112

1213

1314
class ClientBase(object):
@@ -23,6 +24,7 @@ def __init__(
2324
environment: Optional[MutableMapping[str, str]] = None,
2425
edge: Optional[str] = None,
2526
user_agent_extensions: Optional[List[str]] = None,
27+
credential_provider: Optional[CredentialProvider] = None,
2628
):
2729
"""
2830
Initializes the Twilio Client
@@ -35,7 +37,9 @@ def __init__(
3537
:param environment: Environment to look for auth details, defaults to os.environ
3638
:param edge: Twilio Edge to make requests to, defaults to None
3739
:param user_agent_extensions: Additions to the user agent string
40+
:param credential_provider: credential provider for authentication method that needs to be used
3841
"""
42+
3943
environment = environment or os.environ
4044

4145
self.username = username or environment.get("TWILIO_ACCOUNT_SID")
@@ -48,9 +52,8 @@ def __init__(
4852
""" :type : str """
4953
self.user_agent_extensions = user_agent_extensions or []
5054
""" :type : list[str] """
51-
52-
if not self.username or not self.password:
53-
raise TwilioException("Credentials are required to create a TwilioClient")
55+
self.credential_provider = credential_provider or None
56+
""" :type : CredentialProvider """
5457

5558
self.account_sid = account_sid or self.username
5659
""" :type : str """
@@ -69,8 +72,6 @@ def request(
6972
auth: Optional[Tuple[str, str]] = None,
7073
timeout: Optional[float] = None,
7174
allow_redirects: bool = False,
72-
is_oauth: bool = False,
73-
domain: Optional[str] = None,
7475
) -> Response:
7576
"""
7677
Makes a request to the Twilio API using the configured http client
@@ -88,20 +89,26 @@ def request(
8889
:returns: Response from the Twilio API
8990
"""
9091

91-
print('*****')
92-
if not is_oauth:
93-
auth = self.get_auth(auth)
9492
headers = self.get_headers(method, headers)
93+
94+
##If credential provider is provided by user, get the associated auth strategy
95+
##Using the auth strategy, fetch the auth string and set it to authorization header
96+
auth_strategy = None ##Initialization
97+
if self.credential_provider:
98+
print(f'Reached here 2 {self.credential_provider}')
99+
auth_strategy = self.credential_provider.to_auth_strategy()
100+
if auth_strategy.auth_type == AuthType.TOKEN:
101+
auth_strategy.fetch_token()
102+
headers["Authorization"] = auth_strategy.get_auth_string()
103+
if auth_strategy.auth_type == AuthType.BASIC:
104+
headers["Authorization"] = auth_strategy.get_auth_string()
105+
else:
106+
auth = self.get_auth(auth)
107+
108+
print(f'auth2 *** {auth}')
109+
110+
95111
uri = self.get_hostname(uri)
96-
if is_oauth:
97-
OauthTokenBase = dynamic_import(
98-
"twilio.base.oauth_token_base", "OauthTokenBase"
99-
)
100-
token = OauthTokenBase().get_oauth_token(
101-
domain, "v1", self.username, self.password
102-
)
103-
headers["Authorization"] = f"Bearer {token}"
104-
headers.get("Authorization")
105112

106113
return self.http_client.request(
107114
method,
@@ -124,7 +131,6 @@ async def request_async(
124131
auth: Optional[Tuple[str, str]] = None,
125132
timeout: Optional[float] = None,
126133
allow_redirects: bool = False,
127-
is_oauth: bool = False,
128134
) -> Response:
129135
"""
130136
Asynchronously makes a request to the Twilio API using the configured http client
@@ -146,19 +152,25 @@ async def request_async(
146152
raise RuntimeError(
147153
"http_client must be asynchronous to support async API requests"
148154
)
149-
if not is_oauth:
150-
auth = self.get_auth(auth)
155+
156+
151157
headers = self.get_headers(method, headers)
152-
uri = self.get_hostname(uri)
153-
if is_oauth:
154-
OauthTokenBase = dynamic_import(
155-
"twilio.base.oauth_token_base", "OauthTokenBase"
156-
)
157-
token = OauthTokenBase().get_oauth_token(
158-
domain, "v1", self.username, self.password
159-
)
160-
headers["Authorization"] = f"Bearer {token}"
161-
headers.get("Authorization")
158+
159+
##If credential provider is provided by user, get the associated auth strategy
160+
##Using the auth strategy, fetch the auth string and set it to authorization header
161+
auth_strategy = None ##Initialization
162+
if self.credential_provider:
163+
print(f'Reached here 1')
164+
auth_strategy = self.credential_provider.to_auth_strategy()
165+
if auth_strategy.auth_type == AuthType.TOKEN:
166+
auth_strategy.fetch_token()
167+
headers["Authorization"] = auth_strategy.get_auth_string()
168+
if auth_strategy.auth_type == AuthType.BASIC:
169+
headers["Authorization"] = auth_strategy.get_auth_string()
170+
else:
171+
auth = self.get_auth(auth)
172+
173+
print(f'auth2 *** {auth}')
162174

163175
return await self.http_client.request(
164176
method,

twilio/base/domain.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def request(
3232
auth: Optional[Tuple[str, str]] = None,
3333
timeout: Optional[float] = None,
3434
allow_redirects: bool = False,
35-
is_oauth: bool = False,
3635
) -> Response:
3736
"""
3837
Makes an HTTP request to this domain.
@@ -56,8 +55,6 @@ def request(
5655
auth=auth,
5756
timeout=timeout,
5857
allow_redirects=allow_redirects,
59-
is_oauth=is_oauth,
60-
domain=self.base_url,
6158
)
6259

6360
async def request_async(
@@ -70,7 +67,6 @@ async def request_async(
7067
auth: Optional[Tuple[str, str]] = None,
7168
timeout: Optional[float] = None,
7269
allow_redirects: bool = False,
73-
is_oauth: bool = False,
7470
) -> Response:
7571
"""
7672
Makes an asynchronous HTTP request to this domain.
@@ -94,5 +90,4 @@ async def request_async(
9490
auth=auth,
9591
timeout=timeout,
9692
allow_redirects=allow_redirects,
97-
is_oauth=is_oauth,
9893
)

twilio/base/exceptions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ def get_uri(code: int) -> str:
6363
red_error=red("HTTP Error"),
6464
request_was=white("Your request was:"),
6565
http_line=teal("%s %s" % (self.method, self.uri, self.data, self.uri)),
66-
http_line=teal("%s %s" % (self.data, self.headers)),
6766
twilio_returned=white("Twilio returned the following information:"),
6867
message=blue(str(self.msg)),
6968
)

0 commit comments

Comments
 (0)