From b4c5734d8bf364600d4866405a622a09076e21f8 Mon Sep 17 00:00:00 2001 From: Manisha Singh Date: Tue, 23 Jul 2024 15:42:00 +0530 Subject: [PATCH 01/22] feat: oauth sdk implementation (#799) * chore: oauth sdk implementation --- twilio/base/client_base.py | 25 ++- twilio/base/domain.py | 5 + twilio/base/oauth_token_base.py | 24 +++ twilio/base/version.py | 19 +++ twilio/http/bearer_token_http_client.py | 30 ++++ twilio/http/http_client.py | 1 + twilio/http/no_auth_http_client.py | 4 + twilio/http/orgs_token_manager.py | 42 +++++ twilio/http/token_manager.py | 7 + twilio/http/token_manager_initializer.py | 16 ++ twilio/rest/__init__.py | 14 ++ twilio/rest/preview_iam/PreviewIamBase.py | 29 ++++ .../rest/preview_iam/organizations/token.py | 160 ++++++++++++++++++ twilio/twilio_bearer_token_auth.py | 33 ++++ 14 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 twilio/base/oauth_token_base.py create mode 100644 twilio/http/bearer_token_http_client.py create mode 100644 twilio/http/no_auth_http_client.py create mode 100644 twilio/http/orgs_token_manager.py create mode 100644 twilio/http/token_manager.py create mode 100644 twilio/http/token_manager_initializer.py create mode 100644 twilio/rest/preview_iam/PreviewIamBase.py create mode 100644 twilio/rest/preview_iam/organizations/token.py create mode 100644 twilio/twilio_bearer_token_auth.py diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 5f17c7540..700e420ad 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -69,6 +69,8 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, + domain: Optional[str] = None ) -> Response: """ Makes a request to the Twilio API using the configured http client @@ -85,9 +87,15 @@ def request( :returns: Response from the Twilio API """ - auth = self.get_auth(auth) + if not is_oauth: + auth = self.get_auth(auth) headers = self.get_headers(method, headers) uri = self.get_hostname(uri) + if is_oauth: + OauthTokenBase = dynamic_import("twilio.base.oauth_token_base", "OauthTokenBase") + token = OauthTokenBase().get_oauth_token(domain, "v1", self.username, self.password) + headers['Authorization'] = f'Bearer {token}' + headers.get('Authorization') return self.http_client.request( method, @@ -110,6 +118,7 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Asynchronously makes a request to the Twilio API using the configured http client @@ -131,10 +140,15 @@ async def request_async( raise RuntimeError( "http_client must be asynchronous to support async API requests" ) - - auth = self.get_auth(auth) + if not is_oauth: + auth = self.get_auth(auth) headers = self.get_headers(method, headers) uri = self.get_hostname(uri) + if is_oauth: + OauthTokenBase = dynamic_import("twilio.base.oauth_token_base", "OauthTokenBase") + token = OauthTokenBase().get_oauth_token(domain, "v1", self.username, self.password) + headers['Authorization'] = f'Bearer {token}' + headers.get('Authorization') return await self.http_client.request( method, @@ -232,3 +246,8 @@ def __repr__(self) -> str: :returns: Machine friendly representation """ return "".format(self.account_sid) + +def dynamic_import(module_name, class_name): + from importlib import import_module + module = import_module(module_name) + return getattr(module, class_name) diff --git a/twilio/base/domain.py b/twilio/base/domain.py index 4f8395ddf..02f498685 100644 --- a/twilio/base/domain.py +++ b/twilio/base/domain.py @@ -32,6 +32,7 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Makes an HTTP request to this domain. @@ -55,6 +56,8 @@ def request( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth, + domain=self.base_url, ) async def request_async( @@ -67,6 +70,7 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Makes an asynchronous HTTP request to this domain. @@ -90,4 +94,5 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) diff --git a/twilio/base/oauth_token_base.py b/twilio/base/oauth_token_base.py new file mode 100644 index 000000000..91e6d5191 --- /dev/null +++ b/twilio/base/oauth_token_base.py @@ -0,0 +1,24 @@ +from twilio.http.token_manager_initializer import TokenManagerInitializer + +# Dynamic import utility function +def dynamic_import(module_name, class_name): + from importlib import import_module + module = import_module(module_name) + return getattr(module, class_name) + +class OauthTokenBase: + def get_oauth_token(self, domain: str, version: str, username: str, password: str): + Domain = dynamic_import("twilio.base.domain", "Domain") + Version = dynamic_import("twilio.base.version", "Version") + BearerTokenHTTPClient = dynamic_import("twilio.http.bearer_token_http_client", "BearerTokenHTTPClient") + OrgTokenManager = dynamic_import("twilio.http.orgs_token_manager", "OrgTokenManager") + Client = dynamic_import("twilio.rest", "Client") + try: + orgs_token_manager = TokenManagerInitializer.get_token_manager() + return BearerTokenHTTPClient(orgs_token_manager).get_access_token(Version(Domain(Client(username, password), domain), version)) + except Exception: + orgs_token_manager = OrgTokenManager(grant_type='client_credentials', + client_id=username, + client_secret=password) + TokenManagerInitializer().set_token_manager(orgs_token_manager) + return BearerTokenHTTPClient(orgs_token_manager).get_access_token(Version(Domain(Client(username, password), domain), version)) \ No newline at end of file diff --git a/twilio/base/version.py b/twilio/base/version.py index 64cc601fa..e7ddaff6c 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -39,6 +39,7 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Make an HTTP request. @@ -53,6 +54,7 @@ def request( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) async def request_async( @@ -65,6 +67,7 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Make an asynchronous HTTP request @@ -79,6 +82,7 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) @classmethod @@ -123,6 +127,7 @@ def fetch( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Fetch a resource instance. @@ -136,6 +141,7 @@ def fetch( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) return self._parse_fetch(method, uri, response) @@ -150,6 +156,7 @@ async def fetch_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Asynchronously fetch a resource instance. @@ -163,6 +170,7 @@ async def fetch_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) return self._parse_fetch(method, uri, response) @@ -186,6 +194,7 @@ def update( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Update a resource instance. @@ -213,6 +222,7 @@ async def update_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Asynchronously update a resource instance. @@ -226,6 +236,7 @@ async def update_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) return self._parse_update(method, uri, response) @@ -249,6 +260,7 @@ def delete( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> bool: """ Delete a resource. @@ -276,6 +288,7 @@ async def delete_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> bool: """ Asynchronously delete a resource. @@ -289,6 +302,7 @@ async def delete_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) return self._parse_delete(method, uri, response) @@ -347,6 +361,7 @@ async def page_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Makes an asynchronous HTTP request. @@ -360,6 +375,7 @@ async def page_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) def stream( @@ -447,6 +463,7 @@ def create( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Create a resource instance. @@ -474,6 +491,7 @@ async def create_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Any: """ Asynchronously create a resource instance. @@ -487,6 +505,7 @@ async def create_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, + is_oauth=is_oauth ) return self._parse_create(method, uri, response) diff --git a/twilio/http/bearer_token_http_client.py b/twilio/http/bearer_token_http_client.py new file mode 100644 index 000000000..eb5758db9 --- /dev/null +++ b/twilio/http/bearer_token_http_client.py @@ -0,0 +1,30 @@ +import datetime +import jwt + +from twilio.base.version import Version +from twilio.http.token_manager import TokenManager +from twilio.twilio_bearer_token_auth import TwilioBearerTokenAuth + + +class BearerTokenHTTPClient: + def __init__(self, orgs_token_manager: TokenManager): + self.orgs_token_manager = orgs_token_manager + + def get_access_token(self, version: Version): + if TwilioBearerTokenAuth.get_access_token() is None or self.is_token_expired( + TwilioBearerTokenAuth.get_access_token() + ): + access_token = self.orgs_token_manager.fetch_access_token(version) + TwilioBearerTokenAuth.init(access_token) + else: + access_token = TwilioBearerTokenAuth.get_access_token() + + return access_token + + def is_token_expired(self, token): + decoded_jwt = jwt.decode(token, options={"verify_signature": True}) + expires_at = decoded_jwt.get("exp") + # Add a buffer of 30 seconds + buffer_seconds = 30 + buffer_expires_at = expires_at - buffer_seconds + return buffer_expires_at < datetime.datetime.now().timestamp() diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index 9d329c6aa..c97c6e830 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -57,6 +57,7 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, + is_oauth: bool = False, ) -> Response: """ Make an HTTP Request with parameters provided. diff --git a/twilio/http/no_auth_http_client.py b/twilio/http/no_auth_http_client.py new file mode 100644 index 000000000..daa5e5cdc --- /dev/null +++ b/twilio/http/no_auth_http_client.py @@ -0,0 +1,4 @@ +class NoAuthHTTPClient: + def get_headers(self): + headers = {} + return headers diff --git a/twilio/http/orgs_token_manager.py b/twilio/http/orgs_token_manager.py new file mode 100644 index 000000000..fbde521a3 --- /dev/null +++ b/twilio/http/orgs_token_manager.py @@ -0,0 +1,42 @@ +from twilio.base.version import Version +from twilio.http.token_manager import TokenManager +from twilio.rest.preview_iam.organizations.token import TokenList + + +class OrgTokenManager(TokenManager): + """ + Orgs Token Manager + """ + + def __init__( + self, + grant_type: str, + client_id: str, + client_secret: str, + code: str = None, + redirect_uri: str = None, + audience: str = None, + refreshToken: str = None, + scope: str = None, + ): + self.grant_type = grant_type + self.client_id = client_id + self.client_secret = client_secret + self.code = code + self.redirect_uri = redirect_uri + self.audience = audience + self.refreshToken = refreshToken + self.scope = scope + + def fetch_access_token(self, version: Version): + token_list = TokenList(version) + token_instance = token_list.create( + grant_type=self.grant_type, + client_id=self.client_id, + client_secret=self.client_secret, + code=self.code, + redirect_uri=self.redirect_uri, + audience=self.audience, + scope=self.scope, + ) + return token_instance.access_token diff --git a/twilio/http/token_manager.py b/twilio/http/token_manager.py new file mode 100644 index 000000000..28cc73101 --- /dev/null +++ b/twilio/http/token_manager.py @@ -0,0 +1,7 @@ +from twilio.base.version import Version + + +class TokenManager: + + def fetch_access_token(self, version: Version): + pass diff --git a/twilio/http/token_manager_initializer.py b/twilio/http/token_manager_initializer.py new file mode 100644 index 000000000..d4836d68a --- /dev/null +++ b/twilio/http/token_manager_initializer.py @@ -0,0 +1,16 @@ +from twilio.http.token_manager import TokenManager + + +class TokenManagerInitializer: + + org_token_manager = None + + @classmethod + def set_token_manager(cls, token_manager: TokenManager): + cls.org_token_manager = token_manager + + @classmethod + def get_token_manager(cls): + if cls.org_token_manager is None: + raise Exception('Token Manager not initialized') + return cls.org_token_manager \ No newline at end of file diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 31d114a6c..6da8da4a1 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -145,6 +145,7 @@ def __init__( self._numbers: Optional["Numbers"] = None self._oauth: Optional["Oauth"] = None self._preview: Optional["Preview"] = None + self._preview_iam: Optional["PreviewIam"] = None self._pricing: Optional["Pricing"] = None self._proxy: Optional["Proxy"] = None self._routes: Optional["Routes"] = None @@ -446,6 +447,19 @@ def preview(self) -> "Preview": self._preview = Preview(self) return self._preview + @property + def preview_iam(self) -> "PreviewIam": + """ + Access the Preview Twilio Domain + + :returns: Preview Twilio Domain + """ + if self._preview_iam is None: + from twilio.rest.preview_iam import PreviewIam + + self._preview = PreviewIam(self) + return self._preview_iam + @property def pricing(self) -> "Pricing": """ diff --git a/twilio/rest/preview_iam/PreviewIamBase.py b/twilio/rest/preview_iam/PreviewIamBase.py new file mode 100644 index 000000000..9cf1f3bac --- /dev/null +++ b/twilio/rest/preview_iam/PreviewIamBase.py @@ -0,0 +1,29 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" +from twilio.base.domain import Domain +from twilio.rest import Client +from twilio.rest.preview_iam.organizations_openapi.token import Token +from twilio.rest.preview_iam.organizations_openapi.account import Account +from twilio.rest.preview_iam.organizations_openapi.authorize import AuthorizeList +from twilio.rest.preview_iam.organizations_openapi.resource_type import ResourceTypeList +from twilio.rest.preview_iam.organizations_openapi.role_assignment import RoleAssignmentList + +class PreviewIamBase(Domain): + def __init__(self, twilio: Client): + """ + Initialize the PreviewIam Domain + + :returns: Domain for PreviewIam + """ + super().__init__(twilio, "https://preview.twilio.com/iam") + self._token: Optional[TokenList] = None + self._service_accounts: Optional[ServiceAccounts] = None + self._service_roles: Optional[ServiceRoles] = None \ No newline at end of file diff --git a/twilio/rest/preview_iam/organizations/token.py b/twilio/rest/preview_iam/organizations/token.py new file mode 100644 index 000000000..fc28287e8 --- /dev/null +++ b/twilio/rest/preview_iam/organizations/token.py @@ -0,0 +1,160 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Organization Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + + +from datetime import date, datetime +from decimal import Decimal +from typing import Any, Dict, List, Optional, Union, Iterator, AsyncIterator +from twilio.base import deserialize, serialize, values + +from twilio.base.instance_resource import InstanceResource +from twilio.base.list_resource import ListResource +from twilio.base.version import Version + + + +class TokenInstance(InstanceResource): + + """ + :ivar access_token: Token which carries the necessary information to access a Twilio resource directly. + :ivar refresh_token: Token which carries the information necessary to get a new access token. + :ivar id_token: Token which carries the information necessary of user profile. + :ivar token_type: Token type + :ivar expires_in: + """ + + def __init__(self, version: Version, payload: Dict[str, Any]): + super().__init__(version) + + + self.access_token: Optional[str] = payload.get("access_token") + self.refresh_token: Optional[str] = payload.get("refresh_token") + self.id_token: Optional[str] = payload.get("id_token") + self.token_type: Optional[str] = payload.get("token_type") + self.expires_in: Optional[int] = payload.get("expires_in") + + + + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + + return '' + + + + +class TokenList(ListResource): + + def __init__(self, version: Version): + """ + Initialize the TokenList + + :param version: Version that contains the resource + + """ + super().__init__(version) + + + self._uri = '/token' + + + + def create(self, grant_type: str, client_id: str, client_secret: Union[str, object]=values.unset, code: Union[str, object]=values.unset, redirect_uri: Union[str, object]=values.unset, audience: Union[str, object]=values.unset, refresh_token: Union[str, object]=values.unset, scope: Union[str, object]=values.unset) -> TokenInstance: + """ + Create the TokenInstance + + :param grant_type: Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + :param client_id: A 34 character string that uniquely identifies this OAuth App. + :param client_secret: The credential for confidential OAuth App. + :param code: JWT token related to the authorization code grant type. + :param redirect_uri: The redirect uri + :param audience: The targeted audience uri + :param refresh_token: JWT token related to refresh access token. + :param scope: The scope of token + + :returns: The created TokenInstance + """ + + data = values.of({ + 'grant_type': grant_type, + 'client_id': client_id, + 'client_secret': client_secret, + 'code': code, + 'redirect_uri': redirect_uri, + 'audience': audience, + 'refresh_token': refresh_token, + 'scope': scope, + }) + headers = values.of({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) + + + + payload = self._version.create(method='POST', uri=self._uri, data=data, headers=headers) + + return TokenInstance(self._version, payload) + + async def create_async(self, grant_type: str, client_id: str, client_secret: Union[str, object]=values.unset, code: Union[str, object]=values.unset, redirect_uri: Union[str, object]=values.unset, audience: Union[str, object]=values.unset, refresh_token: Union[str, object]=values.unset, scope: Union[str, object]=values.unset) -> TokenInstance: + """ + Asynchronously create the TokenInstance + + :param grant_type: Grant type is a credential representing resource owner's authorization which can be used by client to obtain access token. + :param client_id: A 34 character string that uniquely identifies this OAuth App. + :param client_secret: The credential for confidential OAuth App. + :param code: JWT token related to the authorization code grant type. + :param redirect_uri: The redirect uri + :param audience: The targeted audience uri + :param refresh_token: JWT token related to refresh access token. + :param scope: The scope of token + + :returns: The created TokenInstance + """ + + data = values.of({ + 'grant_type': grant_type, + 'client_id': client_id, + 'client_secret': client_secret, + 'code': code, + 'redirect_uri': redirect_uri, + 'audience': audience, + 'refresh_token': refresh_token, + 'scope': scope, + }) + headers = values.of({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) + + + + payload = await self._version.create_async(method='POST', uri=self._uri, data=data, headers=headers) + + return TokenInstance(self._version, payload) + + + + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return '' + diff --git a/twilio/twilio_bearer_token_auth.py b/twilio/twilio_bearer_token_auth.py new file mode 100644 index 000000000..ffc8814cd --- /dev/null +++ b/twilio/twilio_bearer_token_auth.py @@ -0,0 +1,33 @@ +from threading import Lock + + +class BearerTokenTwilioRestClient: + pass + + +class TwilioBearerTokenAuth: + _lock = Lock() + access_token = None + rest_client = None + user_agent_extensions = None + region = None + edge = None + + @classmethod + def init(cls, access_token): + with cls._lock: + if not access_token: + raise ValueError("Access Token cannot be null or Empty") + if access_token != cls.access_token: + cls.access_token = None + cls.access_token = access_token + + @classmethod + def get_access_token(cls): + with cls._lock: + return cls.access_token + + @classmethod + def get_header_param(cls): + with cls._lock: + return {"Authorization": "Bearer {token}".format(token=cls.access_token)} From 3e246e452b668024609a995af44b02922f79145a Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 16:26:15 +0530 Subject: [PATCH 02/22] Python Orgs Api Changes --- tests/cluster/test_cluster.py | 9 + tests/cluster/test_webhook.py | 156 +++--- twilio/base/client_base.py | 30 +- twilio/base/domain.py | 2 +- twilio/base/exceptions.py | 3 +- twilio/base/oauth_token_base.py | 27 +- twilio/base/version.py | 24 +- twilio/http/token_manager_initializer.py | 4 +- twilio/rest/__init__.py | 14 - twilio/rest/preview_iam/PreviewIamBase.py | 37 +- twilio/rest/preview_iam/__init__.py | 57 ++ .../preview_iam/organizations/__init__.py | 58 ++ .../rest/preview_iam/organizations/account.py | 422 ++++++++++++++ .../organizations/role_assignment.py | 525 ++++++++++++++++++ twilio/rest/preview_iam/v1/__init__.py | 51 ++ twilio/rest/preview_iam/v1/authorize.py | 126 +++++ .../{organizations => v1}/token.py | 140 ++--- 17 files changed, 1488 insertions(+), 197 deletions(-) create mode 100644 twilio/rest/preview_iam/__init__.py create mode 100644 twilio/rest/preview_iam/organizations/__init__.py create mode 100644 twilio/rest/preview_iam/organizations/account.py create mode 100644 twilio/rest/preview_iam/organizations/role_assignment.py create mode 100644 twilio/rest/preview_iam/v1/__init__.py create mode 100644 twilio/rest/preview_iam/v1/authorize.py rename twilio/rest/preview_iam/{organizations => v1}/token.py (59%) diff --git a/tests/cluster/test_cluster.py b/tests/cluster/test_cluster.py index 556fa8da7..76a6f85d0 100644 --- a/tests/cluster/test_cluster.py +++ b/tests/cluster/test_cluster.py @@ -19,6 +19,15 @@ def setUp(self): ) self.voice_twiml = VoiceResponse() + + def test_token_fetch(self): + token = self.client.preview_iam.token.create( + grant_type = GRANT_TYPE, + client_id = CLIENT_ID, + client_secret = CLIENT_SECRET) + print(f'{token}') + + def test_send_text_message(self): msg = self.client.messages.create( to=self.to_number, from_=self.from_number, body="hello world" diff --git a/tests/cluster/test_webhook.py b/tests/cluster/test_webhook.py index 307cf3d39..7cf8fbac8 100644 --- a/tests/cluster/test_webhook.py +++ b/tests/cluster/test_webhook.py @@ -29,81 +29,81 @@ def process_request(self): ) -# class WebhookTest(unittest.TestCase): -# def setUp(self): -# api_key = os.environ["TWILIO_API_KEY"] -# api_secret = os.environ["TWILIO_API_SECRET"] -# account_sid = os.environ["TWILIO_ACCOUNT_SID"] -# self.client = Client(api_key, api_secret, account_sid) -# -# portNumber = 7777 -# self.validation_server = HTTPServer(("", portNumber), RequestHandler) -# self.tunnel = ngrok.connect(portNumber) -# self.flow_sid = "" -# _thread.start_new_thread(self.start_http_server, ()) -# -# def start_http_server(self): -# self.validation_server.serve_forever() -# -# def tearDown(self): -# self.client.studio.v2.flows(self.flow_sid).delete() -# ngrok.kill() -# self.validation_server.shutdown() -# self.validation_server.server_close() -# -# def create_studio_flow(self, url, method): -# flow = self.client.studio.v2.flows.create( -# friendly_name="Python Cluster Test Flow", -# status="published", -# definition={ -# "description": "Studio Flow", -# "states": [ -# { -# "name": "Trigger", -# "type": "trigger", -# "transitions": [ -# { -# "next": "httpRequest", -# "event": "incomingRequest", -# }, -# ], -# "properties": {}, -# }, -# { -# "name": "httpRequest", -# "type": "make-http-request", -# "transitions": [], -# "properties": { -# "method": method, -# "content_type": "application/x-www-form-urlencoded;charset=utf-8", -# "url": url, -# }, -# }, -# ], -# "initial_state": "Trigger", -# "flags": { -# "allow_concurrent_calls": True, -# }, -# }, -# ) -# return flow -# -# def validate(self, method): -# flow = self.create_studio_flow(url=self.tunnel.public_url, method=method) -# self.flow_sid = flow.sid -# time.sleep(5) -# self.client.studio.v2.flows(self.flow_sid).executions.create( -# to="to", from_="from" -# ) -# -# def test_get(self): -# time.sleep(5) -# self.validate("GET") -# time.sleep(5) -# self.assertEqual(RequestHandler.is_request_valid, True) -# -# def test_post(self): -# time.sleep(5) -# self.validate("POST") -# time.sleep(5) -# self.assertEqual(RequestHandler.is_request_valid, True) +class WebhookTest(unittest.TestCase): + def setUp(self): + api_key = os.environ["TWILIO_API_KEY"] + api_secret = os.environ["TWILIO_API_SECRET"] + account_sid = os.environ["TWILIO_ACCOUNT_SID"] + self.client = Client(api_key, api_secret, account_sid) + + portNumber = 7777 + self.validation_server = HTTPServer(("", portNumber), RequestHandler) + self.tunnel = ngrok.connect(portNumber) + self.flow_sid = "" + _thread.start_new_thread(self.start_http_server, ()) + + def start_http_server(self): + self.validation_server.serve_forever() + + def tearDown(self): + self.client.studio.v2.flows(self.flow_sid).delete() + ngrok.kill() + self.validation_server.shutdown() + self.validation_server.server_close() + + def create_studio_flow(self, url, method): + flow = self.client.studio.v2.flows.create( + friendly_name="Python Cluster Test Flow", + status="published", + definition={ + "description": "Studio Flow", + "states": [ + { + "name": "Trigger", + "type": "trigger", + "transitions": [ + { + "next": "httpRequest", + "event": "incomingRequest", + }, + ], + "properties": {}, + }, + { + "name": "httpRequest", + "type": "make-http-request", + "transitions": [], + "properties": { + "method": method, + "content_type": "application/x-www-form-urlencoded;charset=utf-8", + "url": url, + }, + }, + ], + "initial_state": "Trigger", + "flags": { + "allow_concurrent_calls": True, + }, + }, + ) + return flow + + def validate(self, method): + flow = self.create_studio_flow(url=self.tunnel.public_url, method=method) + self.flow_sid = flow.sid + time.sleep(5) + self.client.studio.v2.flows(self.flow_sid).executions.create( + to="to", from_="from" + ) + + def test_get(self): + time.sleep(5) + self.validate("GET") + time.sleep(5) + self.assertEqual(RequestHandler.is_request_valid, True) + + def test_post(self): + time.sleep(5) + self.validate("POST") + time.sleep(5) + self.assertEqual(RequestHandler.is_request_valid, True) diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 700e420ad..1bee0f613 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -70,7 +70,7 @@ def request( timeout: Optional[float] = None, allow_redirects: bool = False, is_oauth: bool = False, - domain: Optional[str] = None + domain: Optional[str] = None, ) -> Response: """ Makes a request to the Twilio API using the configured http client @@ -87,15 +87,21 @@ def request( :returns: Response from the Twilio API """ + + print('*****') if not is_oauth: auth = self.get_auth(auth) headers = self.get_headers(method, headers) uri = self.get_hostname(uri) if is_oauth: - OauthTokenBase = dynamic_import("twilio.base.oauth_token_base", "OauthTokenBase") - token = OauthTokenBase().get_oauth_token(domain, "v1", self.username, self.password) - headers['Authorization'] = f'Bearer {token}' - headers.get('Authorization') + OauthTokenBase = dynamic_import( + "twilio.base.oauth_token_base", "OauthTokenBase" + ) + token = OauthTokenBase().get_oauth_token( + domain, "v1", self.username, self.password + ) + headers["Authorization"] = f"Bearer {token}" + headers.get("Authorization") return self.http_client.request( method, @@ -145,10 +151,14 @@ async def request_async( headers = self.get_headers(method, headers) uri = self.get_hostname(uri) if is_oauth: - OauthTokenBase = dynamic_import("twilio.base.oauth_token_base", "OauthTokenBase") - token = OauthTokenBase().get_oauth_token(domain, "v1", self.username, self.password) - headers['Authorization'] = f'Bearer {token}' - headers.get('Authorization') + OauthTokenBase = dynamic_import( + "twilio.base.oauth_token_base", "OauthTokenBase" + ) + token = OauthTokenBase().get_oauth_token( + domain, "v1", self.username, self.password + ) + headers["Authorization"] = f"Bearer {token}" + headers.get("Authorization") return await self.http_client.request( method, @@ -247,7 +257,9 @@ def __repr__(self) -> str: """ return "".format(self.account_sid) + def dynamic_import(module_name, class_name): from importlib import import_module + module = import_module(module_name) return getattr(module, class_name) diff --git a/twilio/base/domain.py b/twilio/base/domain.py index 02f498685..72724aa9d 100644 --- a/twilio/base/domain.py +++ b/twilio/base/domain.py @@ -94,5 +94,5 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) diff --git a/twilio/base/exceptions.py b/twilio/base/exceptions.py index 8f3b7cc7a..05a7c96e8 100644 --- a/twilio/base/exceptions.py +++ b/twilio/base/exceptions.py @@ -62,7 +62,8 @@ def get_uri(code: int) -> str: "\n\n{twilio_returned}\n\n{message}\n".format( red_error=red("HTTP Error"), request_was=white("Your request was:"), - http_line=teal("%s %s" % (self.method, self.uri)), + http_line=teal("%s %s" % (self.method, self.uri, self.data, self.uri)), + http_line=teal("%s %s" % (self.data, self.headers)), twilio_returned=white("Twilio returned the following information:"), message=blue(str(self.msg)), ) diff --git a/twilio/base/oauth_token_base.py b/twilio/base/oauth_token_base.py index 91e6d5191..f58a143c8 100644 --- a/twilio/base/oauth_token_base.py +++ b/twilio/base/oauth_token_base.py @@ -1,24 +1,37 @@ from twilio.http.token_manager_initializer import TokenManagerInitializer + # Dynamic import utility function def dynamic_import(module_name, class_name): from importlib import import_module + module = import_module(module_name) return getattr(module, class_name) + class OauthTokenBase: def get_oauth_token(self, domain: str, version: str, username: str, password: str): Domain = dynamic_import("twilio.base.domain", "Domain") Version = dynamic_import("twilio.base.version", "Version") - BearerTokenHTTPClient = dynamic_import("twilio.http.bearer_token_http_client", "BearerTokenHTTPClient") - OrgTokenManager = dynamic_import("twilio.http.orgs_token_manager", "OrgTokenManager") + BearerTokenHTTPClient = dynamic_import( + "twilio.http.bearer_token_http_client", "BearerTokenHTTPClient" + ) + OrgTokenManager = dynamic_import( + "twilio.http.orgs_token_manager", "OrgTokenManager" + ) Client = dynamic_import("twilio.rest", "Client") try: orgs_token_manager = TokenManagerInitializer.get_token_manager() - return BearerTokenHTTPClient(orgs_token_manager).get_access_token(Version(Domain(Client(username, password), domain), version)) + return BearerTokenHTTPClient(orgs_token_manager).get_access_token( + Version(Domain(Client(username, password), domain), version) + ) except Exception: - orgs_token_manager = OrgTokenManager(grant_type='client_credentials', - client_id=username, - client_secret=password) + orgs_token_manager = OrgTokenManager( + grant_type="client_credentials", + client_id=username, + client_secret=password, + ) TokenManagerInitializer().set_token_manager(orgs_token_manager) - return BearerTokenHTTPClient(orgs_token_manager).get_access_token(Version(Domain(Client(username, password), domain), version)) \ No newline at end of file + return BearerTokenHTTPClient(orgs_token_manager).get_access_token( + Version(Domain(Client(username, password), domain), version) + ) diff --git a/twilio/base/version.py b/twilio/base/version.py index e7ddaff6c..58fc809af 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -44,6 +44,7 @@ def request( """ Make an HTTP request. """ + print('88888') url = self.relative_uri(uri) return self.domain.request( method, @@ -54,7 +55,7 @@ def request( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) async def request_async( @@ -82,7 +83,7 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) @classmethod @@ -141,7 +142,7 @@ def fetch( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) return self._parse_fetch(method, uri, response) @@ -170,7 +171,7 @@ async def fetch_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) return self._parse_fetch(method, uri, response) @@ -236,7 +237,7 @@ async def update_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) return self._parse_update(method, uri, response) @@ -302,7 +303,7 @@ async def delete_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) return self._parse_delete(method, uri, response) @@ -375,7 +376,7 @@ async def page_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) def stream( @@ -448,6 +449,7 @@ def _parse_create(self, method: str, uri: str, response: Response) -> Any: """ Parse create response JSON """ + print('99999') if response.status_code < 200 or response.status_code >= 300: raise self.exception(method, uri, response, "Unable to create record") @@ -468,6 +470,7 @@ def create( """ Create a resource instance. """ + print('******') response = self.request( method, uri, @@ -478,7 +481,7 @@ def create( timeout=timeout, allow_redirects=allow_redirects, ) - + print('******') return self._parse_create(method, uri, response) async def create_async( @@ -496,6 +499,7 @@ async def create_async( """ Asynchronously create a resource instance. """ + print('******') response = await self.request_async( method, uri, @@ -505,7 +509,7 @@ async def create_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth + is_oauth=is_oauth, ) - + print('******') return self._parse_create(method, uri, response) diff --git a/twilio/http/token_manager_initializer.py b/twilio/http/token_manager_initializer.py index d4836d68a..36ee83f1a 100644 --- a/twilio/http/token_manager_initializer.py +++ b/twilio/http/token_manager_initializer.py @@ -12,5 +12,5 @@ def set_token_manager(cls, token_manager: TokenManager): @classmethod def get_token_manager(cls): if cls.org_token_manager is None: - raise Exception('Token Manager not initialized') - return cls.org_token_manager \ No newline at end of file + raise Exception("Token Manager not initialized") + return cls.org_token_manager diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 6da8da4a1..9c71ce2bb 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -132,7 +132,6 @@ def __init__( self._events: Optional["Events"] = None self._flex_api: Optional["FlexApi"] = None self._frontline_api: Optional["FrontlineApi"] = None - self._preview_iam: Optional["PreviewIam"] = None self._insights: Optional["Insights"] = None self._intelligence: Optional["Intelligence"] = None self._ip_messaging: Optional["IpMessaging"] = None @@ -447,19 +446,6 @@ def preview(self) -> "Preview": self._preview = Preview(self) return self._preview - @property - def preview_iam(self) -> "PreviewIam": - """ - Access the Preview Twilio Domain - - :returns: Preview Twilio Domain - """ - if self._preview_iam is None: - from twilio.rest.preview_iam import PreviewIam - - self._preview = PreviewIam(self) - return self._preview_iam - @property def pricing(self) -> "Pricing": """ diff --git a/twilio/rest/preview_iam/PreviewIamBase.py b/twilio/rest/preview_iam/PreviewIamBase.py index 9cf1f3bac..bcabdda5b 100644 --- a/twilio/rest/preview_iam/PreviewIamBase.py +++ b/twilio/rest/preview_iam/PreviewIamBase.py @@ -8,13 +8,14 @@ https://openapi-generator.tech Do not edit the class manually. """ + from twilio.base.domain import Domain +from typing import Optional from twilio.rest import Client -from twilio.rest.preview_iam.organizations_openapi.token import Token -from twilio.rest.preview_iam.organizations_openapi.account import Account -from twilio.rest.preview_iam.organizations_openapi.authorize import AuthorizeList -from twilio.rest.preview_iam.organizations_openapi.resource_type import ResourceTypeList -from twilio.rest.preview_iam.organizations_openapi.role_assignment import RoleAssignmentList + +from twilio.rest.preview_iam.organizations import Organizations +from twilio.rest.preview_iam.v1 import V1 + class PreviewIamBase(Domain): def __init__(self, twilio: Client): @@ -24,6 +25,26 @@ def __init__(self, twilio: Client): :returns: Domain for PreviewIam """ super().__init__(twilio, "https://preview.twilio.com/iam") - self._token: Optional[TokenList] = None - self._service_accounts: Optional[ServiceAccounts] = None - self._service_roles: Optional[ServiceRoles] = None \ No newline at end of file + self._organizations: Optional[Organizations] = None + self._v1: Optional[V1] = None + # self._token: Optional[TokenList] = None + # self._service_accounts: Optional[ServiceAccounts] = None + # self._service_roles: Optional[ServiceRoles] = None + + @property + def organizations(self) -> Organizations: + """ + :returns: Organizations of PreviewIam + """ + if self._organizations is None: + self._organizations = Organizations(self) + return self._organizations + + @property + def v1(self) -> V1: + """ + :returns: Organizations of PreviewIam + """ + if self._v1 is None: + self._v1 = V1(self) + return self._v1 diff --git a/twilio/rest/preview_iam/__init__.py b/twilio/rest/preview_iam/__init__.py new file mode 100644 index 000000000..f4f47f863 --- /dev/null +++ b/twilio/rest/preview_iam/__init__.py @@ -0,0 +1,57 @@ + +from warnings import warn +from twilio.rest.preview_iam.PreviewIamBase import PreviewIamBase +from twilio.rest.preview_iam.organizations.account import AccountList +from twilio.rest.preview_iam.organizations.role_assignment import RoleAssignmentList + +# from twilio.rest.preview_iam.organizations.user import UserList +from twilio.rest.preview_iam.v1.token import TokenList +from twilio.rest.preview_iam.v1.authorize import AuthorizeList + + +class PreviewIam(PreviewIamBase): + + @property + def accounts(self) -> AccountList: + warn( + "accounts is deprecated. Use organizations.accounts instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.organizations.accounts + + @property + def role_assignments(self) -> RoleAssignmentList: + warn( + "role_assignments is deprecated. Use organizations.role_assignments instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.organizations.role_assignments + + # @property + # def users(self) -> UserList: + # warn( + # "users is deprecated. Use organizations.users instead.", + # DeprecationWarning, + # stacklevel=2, + # ) + # return self.organizations.users + + @property + def token(self) -> TokenList: + warn( + "token is deprecated. Use v1.token instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.v1.token + + @property + def authorize(self) -> AuthorizeList: + warn( + "authorize is deprecated. Use v1.authorize instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.v1.authorize diff --git a/twilio/rest/preview_iam/organizations/__init__.py b/twilio/rest/preview_iam/organizations/__init__.py new file mode 100644 index 000000000..73b726d2e --- /dev/null +++ b/twilio/rest/preview_iam/organizations/__init__.py @@ -0,0 +1,58 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Organization Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + +from typing import Optional +from twilio.base.version import Version +from twilio.base.domain import Domain +from twilio.rest.preview_iam.organizations.account import AccountList +from twilio.rest.preview_iam.organizations.role_assignment import RoleAssignmentList + + +class Organizations(Version): + + def __init__(self, domain: Domain): + """ + Initialize the Organizations version of PreviewIam + + :param domain: The Twilio.preview_iam domain + """ + super().__init__(domain, "Organizations") + self._accounts: Optional[AccountList] = None + self._role_assignments: Optional[RoleAssignmentList] = None + self._users: Optional[UserList] = None + + @property + def accounts(self) -> AccountList: + if self._accounts is None: + self._accounts = AccountList(self) + return self._accounts + + @property + def role_assignments(self) -> RoleAssignmentList: + if self._role_assignments is None: + self._role_assignments = RoleAssignmentList(self) + return self._role_assignments + + # @property + # def users(self) -> UserList: + # if self._users is None: + # self._users = UserList(self) + # return self._users + + def __repr__(self) -> str: + """ + Provide a friendly representation + :returns: Machine friendly representation + """ + return "" diff --git a/twilio/rest/preview_iam/organizations/account.py b/twilio/rest/preview_iam/organizations/account.py new file mode 100644 index 000000000..1b92f6641 --- /dev/null +++ b/twilio/rest/preview_iam/organizations/account.py @@ -0,0 +1,422 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Organization Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + +from datetime import datetime +from typing import Any, Dict, List, Optional, Union, Iterator, AsyncIterator +from twilio.base import deserialize, values +from twilio.base.instance_context import InstanceContext +from twilio.base.instance_resource import InstanceResource +from twilio.base.list_resource import ListResource +from twilio.base.version import Version +from twilio.base.page import Page + + +class AccountInstance(InstanceResource): + """ + :ivar account_sid: Twilio account sid + :ivar friendly_name: Account friendly name + :ivar status: Account status + :ivar owner_sid: Twilio account sid + :ivar date_created: The date and time when the account was created in the system + """ + + def __init__( + self, + version: Version, + payload: Dict[str, Any], + organization_sid: Optional[str] = None, + account_sid: Optional[str] = None, + ): + super().__init__(version) + + self.account_sid: Optional[str] = payload.get("account_sid") + self.friendly_name: Optional[str] = payload.get("friendly_name") + self.status: Optional[str] = payload.get("status") + self.owner_sid: Optional[str] = payload.get("owner_sid") + self.date_created: Optional[datetime] = deserialize.iso8601_datetime( + payload.get("date_created") + ) + + self._solution = { + "organization_sid": organization_sid or self.organization_sid, + "account_sid": account_sid or self.account_sid, + } + self._context: Optional[AccountContext] = None + + @property + def _proxy(self) -> "AccountContext": + """ + Generate an instance context for the instance, the context is capable of + performing various actions. All instance actions are proxied to the context + + :returns: AccountContext for this AccountInstance + """ + if self._context is None: + self._context = AccountContext( + self._version, + organization_sid=self._solution["organization_sid"], + account_sid=self._solution["account_sid"], + ) + return self._context + + def fetch(self) -> "AccountInstance": + """ + Fetch the AccountInstance + + + :returns: The fetched AccountInstance + """ + return self._proxy.fetch() + + async def fetch_async(self) -> "AccountInstance": + """ + Asynchronous coroutine to fetch the AccountInstance + + + :returns: The fetched AccountInstance + """ + return await self._proxy.fetch_async() + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format(context) + + +class AccountContext(InstanceContext): + + def __init__(self, version: Version, organization_sid: str, account_sid: str): + """ + Initialize the AccountContext + + :param version: Version that contains the resource + :param organization_sid: + :param account_sid: + """ + super().__init__(version) + + # Path Solution + self._solution = { + "organization_sid": organization_sid, + "account_sid": account_sid, + } + self._uri = "/{organization_sid}/Accounts/{account_sid}".format( + **self._solution + ) + + def fetch(self) -> AccountInstance: + """ + Fetch the AccountInstance + + + :returns: The fetched AccountInstance + """ + + payload = self._version.fetch( + method="GET", + uri=self._uri, + ) + + return AccountInstance( + self._version, + payload, + organization_sid=self._solution["organization_sid"], + account_sid=self._solution["account_sid"], + ) + + async def fetch_async(self) -> AccountInstance: + """ + Asynchronous coroutine to fetch the AccountInstance + + + :returns: The fetched AccountInstance + """ + + payload = await self._version.fetch_async( + method="GET", + uri=self._uri, + ) + + return AccountInstance( + self._version, + payload, + organization_sid=self._solution["organization_sid"], + account_sid=self._solution["account_sid"], + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format(context) + + +class AccountPage(Page): + + def get_instance(self, payload: Dict[str, Any]) -> AccountInstance: + """ + Build an instance of AccountInstance + + :param payload: Payload response from the API + """ + return AccountInstance( + self._version, payload, organization_sid=self._solution["organization_sid"] + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return "" + + +class AccountList(ListResource): + + def __init__(self, version: Version, organization_sid: str): + """ + Initialize the AccountList + + :param version: Version that contains the resource + :param organization_sid: + + """ + super().__init__(version) + + # Path Solution + self._solution = { + "organization_sid": organization_sid, + } + self._uri = "/{organization_sid}/Accounts".format(**self._solution) + + def stream( + self, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> Iterator[AccountInstance]: + """ + Streams AccountInstance records from the API as a generator stream. + This operation lazily loads records as efficiently as possible until the limit + is reached. + The results are returned as a generator, so this operation is memory efficient. + + :param limit: Upper limit for the number of records to return. stream() + guarantees to never return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, stream() will attempt to read the + limit with the most efficient page size, i.e. min(limit, 1000) + + :returns: Generator that will yield up to limit results + """ + limits = self._version.read_limits(limit, page_size) + page = self.page(page_size=limits["page_size"]) + + return self._version.stream(page, limits["limit"]) + + async def stream_async( + self, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> AsyncIterator[AccountInstance]: + """ + Asynchronously streams AccountInstance records from the API as a generator stream. + This operation lazily loads records as efficiently as possible until the limit + is reached. + The results are returned as a generator, so this operation is memory efficient. + + :param limit: Upper limit for the number of records to return. stream() + guarantees to never return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, stream() will attempt to read the + limit with the most efficient page size, i.e. min(limit, 1000) + + :returns: Generator that will yield up to limit results + """ + limits = self._version.read_limits(limit, page_size) + page = await self.page_async(page_size=limits["page_size"]) + + return self._version.stream_async(page, limits["limit"]) + + def list( + self, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> List[AccountInstance]: + """ + Lists AccountInstance records from the API as a list. + Unlike stream(), this operation is eager and will load `limit` records into + memory before returning. + + :param limit: Upper limit for the number of records to return. list() guarantees + never to return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, list() will attempt to read the limit + with the most efficient page size, i.e. min(limit, 1000) + + :returns: list that will contain up to limit results + """ + return list( + self.stream( + limit=limit, + page_size=page_size, + ) + ) + + async def list_async( + self, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> List[AccountInstance]: + """ + Asynchronously lists AccountInstance records from the API as a list. + Unlike stream(), this operation is eager and will load `limit` records into + memory before returning. + + :param limit: Upper limit for the number of records to return. list() guarantees + never to return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, list() will attempt to read the limit + with the most efficient page size, i.e. min(limit, 1000) + + :returns: list that will contain up to limit results + """ + return [ + record + async for record in await self.stream_async( + limit=limit, + page_size=page_size, + ) + ] + + def page( + self, + page_token: Union[str, object] = values.unset, + page_number: Union[int, object] = values.unset, + page_size: Union[int, object] = values.unset, + ) -> AccountPage: + """ + Retrieve a single page of AccountInstance records from the API. + Request is executed immediately + + :param page_token: PageToken provided by the API + :param page_number: Page Number, this value is simply for client state + :param page_size: Number of records to return, defaults to 50 + + :returns: Page of AccountInstance + """ + data = values.of( + { + "PageToken": page_token, + "Page": page_number, + "PageSize": page_size, + } + ) + + response = self._version.page(method="GET", uri=self._uri, params=data) + return AccountPage(self._version, response, self._solution) + + async def page_async( + self, + page_token: Union[str, object] = values.unset, + page_number: Union[int, object] = values.unset, + page_size: Union[int, object] = values.unset, + ) -> AccountPage: + """ + Asynchronously retrieve a single page of AccountInstance records from the API. + Request is executed immediately + + :param page_token: PageToken provided by the API + :param page_number: Page Number, this value is simply for client state + :param page_size: Number of records to return, defaults to 50 + + :returns: Page of AccountInstance + """ + data = values.of( + { + "PageToken": page_token, + "Page": page_number, + "PageSize": page_size, + } + ) + + response = await self._version.page_async( + method="GET", uri=self._uri, params=data + ) + return AccountPage(self._version, response, self._solution) + + def get_page(self, target_url: str) -> AccountPage: + """ + Retrieve a specific page of AccountInstance records from the API. + Request is executed immediately + + :param target_url: API-generated URL for the requested results page + + :returns: Page of AccountInstance + """ + response = self._version.domain.twilio.request("GET", target_url) + return AccountPage(self._version, response, self._solution) + + async def get_page_async(self, target_url: str) -> AccountPage: + """ + Asynchronously retrieve a specific page of AccountInstance records from the API. + Request is executed immediately + + :param target_url: API-generated URL for the requested results page + + :returns: Page of AccountInstance + """ + response = await self._version.domain.twilio.request_async("GET", target_url) + return AccountPage(self._version, response, self._solution) + + def get(self, organization_sid: str, account_sid: str) -> AccountContext: + """ + Constructs a AccountContext + + :param organization_sid: + :param account_sid: + """ + return AccountContext( + self._version, organization_sid=organization_sid, account_sid=account_sid + ) + + def __call__(self, organization_sid: str, account_sid: str) -> AccountContext: + """ + Constructs a AccountContext + + :param organization_sid: + :param account_sid: + """ + return AccountContext( + self._version, organization_sid=organization_sid, account_sid=account_sid + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return "" diff --git a/twilio/rest/preview_iam/organizations/role_assignment.py b/twilio/rest/preview_iam/organizations/role_assignment.py new file mode 100644 index 000000000..03f9b26ee --- /dev/null +++ b/twilio/rest/preview_iam/organizations/role_assignment.py @@ -0,0 +1,525 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Organization Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + +from typing import Any, Dict, List, Optional, Union, Iterator, AsyncIterator +from twilio.base import values +from twilio.base.instance_context import InstanceContext +from twilio.base.instance_resource import InstanceResource +from twilio.base.list_resource import ListResource +from twilio.base.version import Version +from twilio.base.page import Page + + +class RoleAssignmentInstance(InstanceResource): + """ + :ivar sid: Twilio Role Assignment Sid representing this role assignment + :ivar role_sid: Twilio Role Sid representing assigned role + :ivar scope: Twilio Sid representing identity of this assignment + :ivar identity: Twilio Sid representing scope of this assignment + :ivar code: Twilio-specific error code + :ivar message: Error message + :ivar more_info: Link to Error Code References + :ivar status: HTTP response status code + """ + + def __init__( + self, + version: Version, + payload: Dict[str, Any], + organization_sid: Optional[str] = None, + role_assignment_sid: Optional[str] = None, + ): + super().__init__(version) + + self.sid: Optional[str] = payload.get("sid") + self.role_sid: Optional[str] = payload.get("role_sid") + self.scope: Optional[str] = payload.get("scope") + self.identity: Optional[str] = payload.get("identity") + self.code: Optional[int] = payload.get("code") + self.message: Optional[str] = payload.get("message") + self.more_info: Optional[str] = payload.get("moreInfo") + self.status: Optional[int] = payload.get("status") + + self._solution = { + "organization_sid": organization_sid or self.organization_sid, + "role_assignment_sid": role_assignment_sid or self.role_assignment_sid, + } + self._context: Optional[RoleAssignmentContext] = None + + @property + def _proxy(self) -> "RoleAssignmentContext": + """ + Generate an instance context for the instance, the context is capable of + performing various actions. All instance actions are proxied to the context + + :returns: RoleAssignmentContext for this RoleAssignmentInstance + """ + if self._context is None: + self._context = RoleAssignmentContext( + self._version, + organization_sid=self._solution["organization_sid"], + role_assignment_sid=self._solution["role_assignment_sid"], + ) + return self._context + + def delete(self) -> bool: + """ + Deletes the RoleAssignmentInstance + + + :returns: True if delete succeeds, False otherwise + """ + return self._proxy.delete() + + async def delete_async(self) -> bool: + """ + Asynchronous coroutine that deletes the RoleAssignmentInstance + + + :returns: True if delete succeeds, False otherwise + """ + return await self._proxy.delete_async() + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format( + context + ) + + +class RoleAssignmentContext(InstanceContext): + + def __init__( + self, version: Version, organization_sid: str, role_assignment_sid: str + ): + """ + Initialize the RoleAssignmentContext + + :param version: Version that contains the resource + :param organization_sid: + :param role_assignment_sid: + """ + super().__init__(version) + + # Path Solution + self._solution = { + "organization_sid": organization_sid, + "role_assignment_sid": role_assignment_sid, + } + self._uri = "/{organization_sid}/RoleAssignments/{role_assignment_sid}".format( + **self._solution + ) + + def delete(self) -> bool: + """ + Deletes the RoleAssignmentInstance + + + :returns: True if delete succeeds, False otherwise + """ + return self._version.delete( + method="DELETE", + uri=self._uri, + ) + + async def delete_async(self) -> bool: + """ + Asynchronous coroutine that deletes the RoleAssignmentInstance + + + :returns: True if delete succeeds, False otherwise + """ + return await self._version.delete_async( + method="DELETE", + uri=self._uri, + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + context = " ".join("{}={}".format(k, v) for k, v in self._solution.items()) + return "".format( + context + ) + + +class RoleAssignmentPage(Page): + + def get_instance(self, payload: Dict[str, Any]) -> RoleAssignmentInstance: + """ + Build an instance of RoleAssignmentInstance + + :param payload: Payload response from the API + """ + return RoleAssignmentInstance( + self._version, payload, organization_sid=self._solution["organization_sid"] + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return "" + + +class RoleAssignmentList(ListResource): + + class PublicApiCreateRoleAssignmentRequest(object): + """ + :ivar role_sid: Twilio Role Sid representing assigned role + :ivar scope: Twilio Sid representing scope of this assignment + :ivar identity: Twilio Sid representing identity of this assignment + """ + + def __init__(self, payload: Dict[str, Any]): + + self.role_sid: Optional[str] = payload.get("role_sid") + self.scope: Optional[str] = payload.get("scope") + self.identity: Optional[str] = payload.get("identity") + + def to_dict(self): + return { + "role_sid": self.role_sid, + "scope": self.scope, + "identity": self.identity, + } + + def __init__(self, version: Version, organization_sid: str): + """ + Initialize the RoleAssignmentList + + :param version: Version that contains the resource + :param organization_sid: + + """ + super().__init__(version) + + # Path Solution + self._solution = { + "organization_sid": organization_sid, + } + self._uri = "/{organization_sid}/RoleAssignments".format(**self._solution) + + def create( + self, + public_api_create_role_assignment_request: PublicApiCreateRoleAssignmentRequest, + ) -> RoleAssignmentInstance: + """ + Create the RoleAssignmentInstance + + :param public_api_create_role_assignment_request: + + :returns: The created RoleAssignmentInstance + """ + data = public_api_create_role_assignment_request.to_dict() + + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + headers["Content-Type"] = "application/json" + + payload = self._version.create( + method="POST", uri=self._uri, data=data, headers=headers + ) + + return RoleAssignmentInstance( + self._version, payload, organization_sid=self._solution["organization_sid"] + ) + + async def create_async( + self, + public_api_create_role_assignment_request: PublicApiCreateRoleAssignmentRequest, + ) -> RoleAssignmentInstance: + """ + Asynchronously create the RoleAssignmentInstance + + :param public_api_create_role_assignment_request: + + :returns: The created RoleAssignmentInstance + """ + data = public_api_create_role_assignment_request.to_dict() + + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + headers["Content-Type"] = "application/json" + + payload = await self._version.create_async( + method="POST", uri=self._uri, data=data, headers=headers + ) + + return RoleAssignmentInstance( + self._version, payload, organization_sid=self._solution["organization_sid"] + ) + + def stream( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> Iterator[RoleAssignmentInstance]: + """ + Streams RoleAssignmentInstance records from the API as a generator stream. + This operation lazily loads records as efficiently as possible until the limit + is reached. + The results are returned as a generator, so this operation is memory efficient. + + :param str identity: + :param str scope: + :param limit: Upper limit for the number of records to return. stream() + guarantees to never return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, stream() will attempt to read the + limit with the most efficient page size, i.e. min(limit, 1000) + + :returns: Generator that will yield up to limit results + """ + limits = self._version.read_limits(limit, page_size) + page = self.page(identity=identity, scope=scope, page_size=limits["page_size"]) + + return self._version.stream(page, limits["limit"]) + + async def stream_async( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> AsyncIterator[RoleAssignmentInstance]: + """ + Asynchronously streams RoleAssignmentInstance records from the API as a generator stream. + This operation lazily loads records as efficiently as possible until the limit + is reached. + The results are returned as a generator, so this operation is memory efficient. + + :param str identity: + :param str scope: + :param limit: Upper limit for the number of records to return. stream() + guarantees to never return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, stream() will attempt to read the + limit with the most efficient page size, i.e. min(limit, 1000) + + :returns: Generator that will yield up to limit results + """ + limits = self._version.read_limits(limit, page_size) + page = await self.page_async( + identity=identity, scope=scope, page_size=limits["page_size"] + ) + + return self._version.stream_async(page, limits["limit"]) + + def list( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> List[RoleAssignmentInstance]: + """ + Lists RoleAssignmentInstance records from the API as a list. + Unlike stream(), this operation is eager and will load `limit` records into + memory before returning. + + :param str identity: + :param str scope: + :param limit: Upper limit for the number of records to return. list() guarantees + never to return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, list() will attempt to read the limit + with the most efficient page size, i.e. min(limit, 1000) + + :returns: list that will contain up to limit results + """ + return list( + self.stream( + identity=identity, + scope=scope, + limit=limit, + page_size=page_size, + ) + ) + + async def list_async( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + limit: Optional[int] = None, + page_size: Optional[int] = None, + ) -> List[RoleAssignmentInstance]: + """ + Asynchronously lists RoleAssignmentInstance records from the API as a list. + Unlike stream(), this operation is eager and will load `limit` records into + memory before returning. + + :param str identity: + :param str scope: + :param limit: Upper limit for the number of records to return. list() guarantees + never to return more than limit. Default is no limit + :param page_size: Number of records to fetch per request, when not set will use + the default value of 50 records. If no page_size is defined + but a limit is defined, list() will attempt to read the limit + with the most efficient page size, i.e. min(limit, 1000) + + :returns: list that will contain up to limit results + """ + return [ + record + async for record in await self.stream_async( + identity=identity, + scope=scope, + limit=limit, + page_size=page_size, + ) + ] + + def page( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + page_token: Union[str, object] = values.unset, + page_number: Union[int, object] = values.unset, + page_size: Union[int, object] = values.unset, + ) -> RoleAssignmentPage: + """ + Retrieve a single page of RoleAssignmentInstance records from the API. + Request is executed immediately + + :param identity: + :param scope: + :param page_token: PageToken provided by the API + :param page_number: Page Number, this value is simply for client state + :param page_size: Number of records to return, defaults to 50 + + :returns: Page of RoleAssignmentInstance + """ + data = values.of( + { + "Identity": identity, + "Scope": scope, + "PageToken": page_token, + "Page": page_number, + "PageSize": page_size, + } + ) + + response = self._version.page(method="GET", uri=self._uri, params=data) + return RoleAssignmentPage(self._version, response, self._solution) + + async def page_async( + self, + identity: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + page_token: Union[str, object] = values.unset, + page_number: Union[int, object] = values.unset, + page_size: Union[int, object] = values.unset, + ) -> RoleAssignmentPage: + """ + Asynchronously retrieve a single page of RoleAssignmentInstance records from the API. + Request is executed immediately + + :param identity: + :param scope: + :param page_token: PageToken provided by the API + :param page_number: Page Number, this value is simply for client state + :param page_size: Number of records to return, defaults to 50 + + :returns: Page of RoleAssignmentInstance + """ + data = values.of( + { + "Identity": identity, + "Scope": scope, + "PageToken": page_token, + "Page": page_number, + "PageSize": page_size, + } + ) + + response = await self._version.page_async( + method="GET", uri=self._uri, params=data + ) + return RoleAssignmentPage(self._version, response, self._solution) + + def get_page(self, target_url: str) -> RoleAssignmentPage: + """ + Retrieve a specific page of RoleAssignmentInstance records from the API. + Request is executed immediately + + :param target_url: API-generated URL for the requested results page + + :returns: Page of RoleAssignmentInstance + """ + response = self._version.domain.twilio.request("GET", target_url) + return RoleAssignmentPage(self._version, response, self._solution) + + async def get_page_async(self, target_url: str) -> RoleAssignmentPage: + """ + Asynchronously retrieve a specific page of RoleAssignmentInstance records from the API. + Request is executed immediately + + :param target_url: API-generated URL for the requested results page + + :returns: Page of RoleAssignmentInstance + """ + response = await self._version.domain.twilio.request_async("GET", target_url) + return RoleAssignmentPage(self._version, response, self._solution) + + def get( + self, organization_sid: str, role_assignment_sid: str + ) -> RoleAssignmentContext: + """ + Constructs a RoleAssignmentContext + + :param organization_sid: + :param role_assignment_sid: + """ + return RoleAssignmentContext( + self._version, + organization_sid=organization_sid, + role_assignment_sid=role_assignment_sid, + ) + + def __call__( + self, organization_sid: str, role_assignment_sid: str + ) -> RoleAssignmentContext: + """ + Constructs a RoleAssignmentContext + + :param organization_sid: + :param role_assignment_sid: + """ + return RoleAssignmentContext( + self._version, + organization_sid=organization_sid, + role_assignment_sid=role_assignment_sid, + ) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return "" diff --git a/twilio/rest/preview_iam/v1/__init__.py b/twilio/rest/preview_iam/v1/__init__.py new file mode 100644 index 000000000..881bd44f6 --- /dev/null +++ b/twilio/rest/preview_iam/v1/__init__.py @@ -0,0 +1,51 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + V1 Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + +from typing import Optional +from twilio.base.version import Version +from twilio.base.domain import Domain +from twilio.rest.preview_iam.v1.token import TokenList +from twilio.rest.preview_iam.v1.authorize import AuthorizeList + + +class V1(Version): + + def __init__(self, domain: Domain): + """ + Initialize the V1 version of PreviewIam + + :param domain: The Twilio.preview_iam domain + """ + super().__init__(domain, "V1") + self._token: Optional[TokenList] = None + self._authorize: Optional[AuthorizeList] = None + + @property + def token(self) -> TokenList: + if self._token is None: + self._token = TokenList(self) + return self._token + + @property + def authorize(self) -> AuthorizeList: + if self._authorize is None: + self._authorize = AuthorizeList(self) + return self._authorize + + def __repr__(self) -> str: + """ + Provide a friendly representation + :returns: Machine friendly representation + """ + return "" diff --git a/twilio/rest/preview_iam/v1/authorize.py b/twilio/rest/preview_iam/v1/authorize.py new file mode 100644 index 000000000..051f13a8e --- /dev/null +++ b/twilio/rest/preview_iam/v1/authorize.py @@ -0,0 +1,126 @@ +r""" + This code was generated by + ___ _ _ _ _ _ _ ____ ____ ____ _ ____ ____ _ _ ____ ____ ____ ___ __ __ + | | | | | | | | | __ | | |__| | __ | __ |___ |\ | |___ |__/ |__| | | | |__/ + | |_|_| | |___ | |__| |__| | | | |__] |___ | \| |___ | \ | | | |__| | \ + + Organization Public API + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + NOTE: This class is auto generated by OpenAPI Generator. + https://openapi-generator.tech + Do not edit the class manually. +""" + +from typing import Any, Dict, Optional, Union +from twilio.base import values + +from twilio.base.instance_resource import InstanceResource +from twilio.base.list_resource import ListResource +from twilio.base.version import Version + + +class AuthorizeInstance(InstanceResource): + """ + :ivar redirect_to: The callback URL + """ + + def __init__(self, version: Version, payload: Dict[str, Any]): + super().__init__(version) + + self.redirect_to: Optional[str] = payload.get("redirect_to") + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + + return "" + + +class AuthorizeList(ListResource): + + def __init__(self, version: Version): + """ + Initialize the AuthorizeList + + :param version: Version that contains the resource + + """ + super().__init__(version) + + self._uri = "/authorize" + + def fetch( + self, + response_type: Union[str, object] = values.unset, + client_id: Union[str, object] = values.unset, + redirect_uri: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + state: Union[str, object] = values.unset, + ) -> AuthorizeInstance: + """ + Asynchronously fetch the AuthorizeInstance + + :param response_type: Response Type:param client_id: The Client Identifier:param redirect_uri: The url to which response will be redirected to:param scope: The scope of the access request:param state: An opaque value which can be used to maintain state between the request and callback + :returns: The fetched AuthorizeInstance + """ + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + + params = values.of( + { + "response_type": response_type, + "client_id": client_id, + "redirect_uri": redirect_uri, + "scope": scope, + "state": state, + } + ) + + payload = self._version.fetch( + method="GET", uri=self._uri, headers=headers, params=params + ) + + return AuthorizeInstance(self._version, payload) + + async def fetch_async( + self, + response_type: Union[str, object] = values.unset, + client_id: Union[str, object] = values.unset, + redirect_uri: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + state: Union[str, object] = values.unset, + ) -> AuthorizeInstance: + """ + Asynchronously fetch the AuthorizeInstance + + :param response_type: Response Type:param client_id: The Client Identifier:param redirect_uri: The url to which response will be redirected to:param scope: The scope of the access request:param state: An opaque value which can be used to maintain state between the request and callback + :returns: The fetched AuthorizeInstance + """ + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + + params = values.of( + { + "response_type": response_type, + "client_id": client_id, + "redirect_uri": redirect_uri, + "scope": scope, + "state": state, + } + ) + + payload = await self._version.fetch_async( + method="GET", uri=self._uri, headers=headers, params=params + ) + + return AuthorizeInstance(self._version, payload) + + def __repr__(self) -> str: + """ + Provide a friendly representation + + :returns: Machine friendly representation + """ + return "" diff --git a/twilio/rest/preview_iam/organizations/token.py b/twilio/rest/preview_iam/v1/token.py similarity index 59% rename from twilio/rest/preview_iam/organizations/token.py rename to twilio/rest/preview_iam/v1/token.py index fc28287e8..9dc953ff3 100644 --- a/twilio/rest/preview_iam/organizations/token.py +++ b/twilio/rest/preview_iam/v1/token.py @@ -12,70 +12,66 @@ Do not edit the class manually. """ - -from datetime import date, datetime -from decimal import Decimal -from typing import Any, Dict, List, Optional, Union, Iterator, AsyncIterator -from twilio.base import deserialize, serialize, values +from typing import Any, Dict, Optional, Union +from twilio.base import values from twilio.base.instance_resource import InstanceResource from twilio.base.list_resource import ListResource from twilio.base.version import Version - class TokenInstance(InstanceResource): - """ :ivar access_token: Token which carries the necessary information to access a Twilio resource directly. :ivar refresh_token: Token which carries the information necessary to get a new access token. :ivar id_token: Token which carries the information necessary of user profile. :ivar token_type: Token type - :ivar expires_in: + :ivar expires_in: """ def __init__(self, version: Version, payload: Dict[str, Any]): super().__init__(version) - self.access_token: Optional[str] = payload.get("access_token") self.refresh_token: Optional[str] = payload.get("refresh_token") self.id_token: Optional[str] = payload.get("id_token") self.token_type: Optional[str] = payload.get("token_type") self.expires_in: Optional[int] = payload.get("expires_in") - - - def __repr__(self) -> str: """ Provide a friendly representation :returns: Machine friendly representation """ - - return '' - + return "" class TokenList(ListResource): - + def __init__(self, version: Version): """ Initialize the TokenList :param version: Version that contains the resource - + """ super().__init__(version) - - self._uri = '/token' - - - - def create(self, grant_type: str, client_id: str, client_secret: Union[str, object]=values.unset, code: Union[str, object]=values.unset, redirect_uri: Union[str, object]=values.unset, audience: Union[str, object]=values.unset, refresh_token: Union[str, object]=values.unset, scope: Union[str, object]=values.unset) -> TokenInstance: + self._uri = "https://preview-iam.twilio.com/v1/token" + + def create( + self, + grant_type: str, + client_id: str, + client_secret: Union[str, object] = values.unset, + code: Union[str, object] = values.unset, + redirect_uri: Union[str, object] = values.unset, + audience: Union[str, object] = values.unset, + refresh_token: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + ) -> TokenInstance: """ Create the TokenInstance @@ -87,31 +83,45 @@ def create(self, grant_type: str, client_id: str, client_secret: Union[str, obje :param audience: The targeted audience uri :param refresh_token: JWT token related to refresh access token. :param scope: The scope of token - + :returns: The created TokenInstance """ - - data = values.of({ - 'grant_type': grant_type, - 'client_id': client_id, - 'client_secret': client_secret, - 'code': code, - 'redirect_uri': redirect_uri, - 'audience': audience, - 'refresh_token': refresh_token, - 'scope': scope, - }) - headers = values.of({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) - - - - payload = self._version.create(method='POST', uri=self._uri, data=data, headers=headers) + data = values.of( + { + "grant_type": grant_type, + "client_id": client_id, + "client_secret": client_secret, + "code": code, + "redirect_uri": redirect_uri, + "audience": audience, + "refresh_token": refresh_token, + "scope": scope, + } + ) + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + print('.......') + print(f'{data}') + print('.......') + print(f'{headers}') + print('.......') + payload = self._version.create( + method="POST", uri=self._uri, data=data, headers=headers + ) + print('rest method output...') return TokenInstance(self._version, payload) - async def create_async(self, grant_type: str, client_id: str, client_secret: Union[str, object]=values.unset, code: Union[str, object]=values.unset, redirect_uri: Union[str, object]=values.unset, audience: Union[str, object]=values.unset, refresh_token: Union[str, object]=values.unset, scope: Union[str, object]=values.unset) -> TokenInstance: + async def create_async( + self, + grant_type: str, + client_id: str, + client_secret: Union[str, object] = values.unset, + code: Union[str, object] = values.unset, + redirect_uri: Union[str, object] = values.unset, + audience: Union[str, object] = values.unset, + refresh_token: Union[str, object] = values.unset, + scope: Union[str, object] = values.unset, + ) -> TokenInstance: """ Asynchronously create the TokenInstance @@ -123,32 +133,29 @@ async def create_async(self, grant_type: str, client_id: str, client_secret: Uni :param audience: The targeted audience uri :param refresh_token: JWT token related to refresh access token. :param scope: The scope of token - + :returns: The created TokenInstance """ - - data = values.of({ - 'grant_type': grant_type, - 'client_id': client_id, - 'client_secret': client_secret, - 'code': code, - 'redirect_uri': redirect_uri, - 'audience': audience, - 'refresh_token': refresh_token, - 'scope': scope, - }) - headers = values.of({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) - - - - payload = await self._version.create_async(method='POST', uri=self._uri, data=data, headers=headers) - - return TokenInstance(self._version, payload) - + data = values.of( + { + "grant_type": grant_type, + "client_id": client_id, + "client_secret": client_secret, + "code": code, + "redirect_uri": redirect_uri, + "audience": audience, + "refresh_token": refresh_token, + "scope": scope, + } + ) + headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + + payload = await self._version.create_async( + method="POST", uri=self._uri, data=data, headers=headers + ) + return TokenInstance(self._version, payload) def __repr__(self) -> str: """ @@ -156,5 +163,4 @@ def __repr__(self) -> str: :returns: Machine friendly representation """ - return '' - + return "" From 83954872bab75b1ef4664a70595e7cb47d9f861e Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 16:28:36 +0530 Subject: [PATCH 03/22] removing unwanted logs --- twilio/base/version.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/twilio/base/version.py b/twilio/base/version.py index 58fc809af..9e40fe90b 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -470,7 +470,6 @@ def create( """ Create a resource instance. """ - print('******') response = self.request( method, uri, @@ -481,7 +480,6 @@ def create( timeout=timeout, allow_redirects=allow_redirects, ) - print('******') return self._parse_create(method, uri, response) async def create_async( @@ -499,7 +497,6 @@ async def create_async( """ Asynchronously create a resource instance. """ - print('******') response = await self.request_async( method, uri, @@ -511,5 +508,4 @@ async def create_async( allow_redirects=allow_redirects, is_oauth=is_oauth, ) - print('******') return self._parse_create(method, uri, response) From bc5c16b691c5702246b31fbbfb2652694285cd1e Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 16:29:41 +0530 Subject: [PATCH 04/22] removing unwanted logs --- twilio/base/version.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/twilio/base/version.py b/twilio/base/version.py index 9e40fe90b..68cf5ca30 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -44,7 +44,6 @@ def request( """ Make an HTTP request. """ - print('88888') url = self.relative_uri(uri) return self.domain.request( method, @@ -449,7 +448,6 @@ def _parse_create(self, method: str, uri: str, response: Response) -> Any: """ Parse create response JSON """ - print('99999') if response.status_code < 200 or response.status_code >= 300: raise self.exception(method, uri, response, "Unable to create record") From a66f9e9d1fd0735c8c6a9959afe1cf047660b469 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 16:30:41 +0530 Subject: [PATCH 05/22] removing unwanted logs --- tests/cluster/test_webhook.py | 156 +++++++++++++++++----------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/tests/cluster/test_webhook.py b/tests/cluster/test_webhook.py index 7cf8fbac8..307cf3d39 100644 --- a/tests/cluster/test_webhook.py +++ b/tests/cluster/test_webhook.py @@ -29,81 +29,81 @@ def process_request(self): ) -class WebhookTest(unittest.TestCase): - def setUp(self): - api_key = os.environ["TWILIO_API_KEY"] - api_secret = os.environ["TWILIO_API_SECRET"] - account_sid = os.environ["TWILIO_ACCOUNT_SID"] - self.client = Client(api_key, api_secret, account_sid) - - portNumber = 7777 - self.validation_server = HTTPServer(("", portNumber), RequestHandler) - self.tunnel = ngrok.connect(portNumber) - self.flow_sid = "" - _thread.start_new_thread(self.start_http_server, ()) - - def start_http_server(self): - self.validation_server.serve_forever() - - def tearDown(self): - self.client.studio.v2.flows(self.flow_sid).delete() - ngrok.kill() - self.validation_server.shutdown() - self.validation_server.server_close() - - def create_studio_flow(self, url, method): - flow = self.client.studio.v2.flows.create( - friendly_name="Python Cluster Test Flow", - status="published", - definition={ - "description": "Studio Flow", - "states": [ - { - "name": "Trigger", - "type": "trigger", - "transitions": [ - { - "next": "httpRequest", - "event": "incomingRequest", - }, - ], - "properties": {}, - }, - { - "name": "httpRequest", - "type": "make-http-request", - "transitions": [], - "properties": { - "method": method, - "content_type": "application/x-www-form-urlencoded;charset=utf-8", - "url": url, - }, - }, - ], - "initial_state": "Trigger", - "flags": { - "allow_concurrent_calls": True, - }, - }, - ) - return flow - - def validate(self, method): - flow = self.create_studio_flow(url=self.tunnel.public_url, method=method) - self.flow_sid = flow.sid - time.sleep(5) - self.client.studio.v2.flows(self.flow_sid).executions.create( - to="to", from_="from" - ) - - def test_get(self): - time.sleep(5) - self.validate("GET") - time.sleep(5) - self.assertEqual(RequestHandler.is_request_valid, True) - - def test_post(self): - time.sleep(5) - self.validate("POST") - time.sleep(5) - self.assertEqual(RequestHandler.is_request_valid, True) +# class WebhookTest(unittest.TestCase): +# def setUp(self): +# api_key = os.environ["TWILIO_API_KEY"] +# api_secret = os.environ["TWILIO_API_SECRET"] +# account_sid = os.environ["TWILIO_ACCOUNT_SID"] +# self.client = Client(api_key, api_secret, account_sid) +# +# portNumber = 7777 +# self.validation_server = HTTPServer(("", portNumber), RequestHandler) +# self.tunnel = ngrok.connect(portNumber) +# self.flow_sid = "" +# _thread.start_new_thread(self.start_http_server, ()) +# +# def start_http_server(self): +# self.validation_server.serve_forever() +# +# def tearDown(self): +# self.client.studio.v2.flows(self.flow_sid).delete() +# ngrok.kill() +# self.validation_server.shutdown() +# self.validation_server.server_close() +# +# def create_studio_flow(self, url, method): +# flow = self.client.studio.v2.flows.create( +# friendly_name="Python Cluster Test Flow", +# status="published", +# definition={ +# "description": "Studio Flow", +# "states": [ +# { +# "name": "Trigger", +# "type": "trigger", +# "transitions": [ +# { +# "next": "httpRequest", +# "event": "incomingRequest", +# }, +# ], +# "properties": {}, +# }, +# { +# "name": "httpRequest", +# "type": "make-http-request", +# "transitions": [], +# "properties": { +# "method": method, +# "content_type": "application/x-www-form-urlencoded;charset=utf-8", +# "url": url, +# }, +# }, +# ], +# "initial_state": "Trigger", +# "flags": { +# "allow_concurrent_calls": True, +# }, +# }, +# ) +# return flow +# +# def validate(self, method): +# flow = self.create_studio_flow(url=self.tunnel.public_url, method=method) +# self.flow_sid = flow.sid +# time.sleep(5) +# self.client.studio.v2.flows(self.flow_sid).executions.create( +# to="to", from_="from" +# ) +# +# def test_get(self): +# time.sleep(5) +# self.validate("GET") +# time.sleep(5) +# self.assertEqual(RequestHandler.is_request_valid, True) +# +# def test_post(self): +# time.sleep(5) +# self.validate("POST") +# time.sleep(5) +# self.assertEqual(RequestHandler.is_request_valid, True) From b5a649032052daef3f1b9231f04c860b90de1b92 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 16:31:37 +0530 Subject: [PATCH 06/22] removing unwanted logs --- twilio/rest/preview_iam/v1/token.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/twilio/rest/preview_iam/v1/token.py b/twilio/rest/preview_iam/v1/token.py index 9dc953ff3..ac0a198ce 100644 --- a/twilio/rest/preview_iam/v1/token.py +++ b/twilio/rest/preview_iam/v1/token.py @@ -100,11 +100,8 @@ def create( } ) headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) - print('.......') print(f'{data}') - print('.......') print(f'{headers}') - print('.......') payload = self._version.create( method="POST", uri=self._uri, data=data, headers=headers ) From fac26eec4e513160d579e706fdd55802ad6f7e0b Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 17 Sep 2024 19:17:33 +0530 Subject: [PATCH 07/22] Fixing token fetch flow --- twilio/http/orgs_token_manager.py | 2 +- twilio/rest/preview_iam/PreviewIamBase.py | 2 +- twilio/rest/preview_iam/__init__.py | 25 ------------------- .../preview_iam/organizations/__init__.py | 2 +- twilio/rest/preview_iam/v1/__init__.py | 2 +- twilio/rest/preview_iam/v1/token.py | 5 +--- 6 files changed, 5 insertions(+), 33 deletions(-) diff --git a/twilio/http/orgs_token_manager.py b/twilio/http/orgs_token_manager.py index fbde521a3..1929f21a5 100644 --- a/twilio/http/orgs_token_manager.py +++ b/twilio/http/orgs_token_manager.py @@ -1,6 +1,6 @@ from twilio.base.version import Version from twilio.http.token_manager import TokenManager -from twilio.rest.preview_iam.organizations.token import TokenList +from twilio.rest.preview_iam.v1.token import TokenList class OrgTokenManager(TokenManager): diff --git a/twilio/rest/preview_iam/PreviewIamBase.py b/twilio/rest/preview_iam/PreviewIamBase.py index bcabdda5b..c46b0463d 100644 --- a/twilio/rest/preview_iam/PreviewIamBase.py +++ b/twilio/rest/preview_iam/PreviewIamBase.py @@ -24,7 +24,7 @@ def __init__(self, twilio: Client): :returns: Domain for PreviewIam """ - super().__init__(twilio, "https://preview.twilio.com/iam") + super().__init__(twilio, "https://preview-iam.twilio.com") self._organizations: Optional[Organizations] = None self._v1: Optional[V1] = None # self._token: Optional[TokenList] = None diff --git a/twilio/rest/preview_iam/__init__.py b/twilio/rest/preview_iam/__init__.py index f4f47f863..7ae6b65ab 100644 --- a/twilio/rest/preview_iam/__init__.py +++ b/twilio/rest/preview_iam/__init__.py @@ -13,45 +13,20 @@ class PreviewIam(PreviewIamBase): @property def accounts(self) -> AccountList: - warn( - "accounts is deprecated. Use organizations.accounts instead.", - DeprecationWarning, - stacklevel=2, - ) return self.organizations.accounts @property def role_assignments(self) -> RoleAssignmentList: - warn( - "role_assignments is deprecated. Use organizations.role_assignments instead.", - DeprecationWarning, - stacklevel=2, - ) return self.organizations.role_assignments # @property # def users(self) -> UserList: - # warn( - # "users is deprecated. Use organizations.users instead.", - # DeprecationWarning, - # stacklevel=2, - # ) # return self.organizations.users @property def token(self) -> TokenList: - warn( - "token is deprecated. Use v1.token instead.", - DeprecationWarning, - stacklevel=2, - ) return self.v1.token @property def authorize(self) -> AuthorizeList: - warn( - "authorize is deprecated. Use v1.authorize instead.", - DeprecationWarning, - stacklevel=2, - ) return self.v1.authorize diff --git a/twilio/rest/preview_iam/organizations/__init__.py b/twilio/rest/preview_iam/organizations/__init__.py index 73b726d2e..9cd6cd7a6 100644 --- a/twilio/rest/preview_iam/organizations/__init__.py +++ b/twilio/rest/preview_iam/organizations/__init__.py @@ -27,7 +27,7 @@ def __init__(self, domain: Domain): :param domain: The Twilio.preview_iam domain """ - super().__init__(domain, "Organizations") + super().__init__(domain, "organizations") self._accounts: Optional[AccountList] = None self._role_assignments: Optional[RoleAssignmentList] = None self._users: Optional[UserList] = None diff --git a/twilio/rest/preview_iam/v1/__init__.py b/twilio/rest/preview_iam/v1/__init__.py index 881bd44f6..396dc40c6 100644 --- a/twilio/rest/preview_iam/v1/__init__.py +++ b/twilio/rest/preview_iam/v1/__init__.py @@ -27,7 +27,7 @@ def __init__(self, domain: Domain): :param domain: The Twilio.preview_iam domain """ - super().__init__(domain, "V1") + super().__init__(domain, "v1") self._token: Optional[TokenList] = None self._authorize: Optional[AuthorizeList] = None diff --git a/twilio/rest/preview_iam/v1/token.py b/twilio/rest/preview_iam/v1/token.py index ac0a198ce..50e37b9ea 100644 --- a/twilio/rest/preview_iam/v1/token.py +++ b/twilio/rest/preview_iam/v1/token.py @@ -59,7 +59,7 @@ def __init__(self, version: Version): """ super().__init__(version) - self._uri = "https://preview-iam.twilio.com/v1/token" + self._uri = "/token" def create( self, @@ -100,12 +100,9 @@ def create( } ) headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) - print(f'{data}') - print(f'{headers}') payload = self._version.create( method="POST", uri=self._uri, data=data, headers=headers ) - print('rest method output...') return TokenInstance(self._version, payload) async def create_async( From 15e15c0169725a4f254f54142d967c5494a756f4 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 17:56:00 +0530 Subject: [PATCH 08/22] twilio python changes for orgs api uptake --- twilio/authStrategy/authStrategy.py | 21 ++++++ twilio/authStrategy/authType.py | 11 +++ twilio/authStrategy/basicAuthStrategy.py | 17 +++++ twilio/authStrategy/noAuthStrategy.py | 11 +++ twilio/authStrategy/tokenAuthStrategy.py | 35 +++++++++ twilio/base/client_base.py | 74 +++++++++++-------- twilio/base/domain.py | 5 -- twilio/base/exceptions.py | 1 - twilio/base/version.py | 16 ---- twilio/credential/credentialProvider.py | 12 +++ twilio/credential/orgsCredentialProvider.py | 26 +++++++ twilio/http/http_client.py | 11 ++- twilio/http/orgs_token_manager.py | 8 +- twilio/rest/__init__.py | 3 + .../preview_iam/organizations/__init__.py | 12 ++- twilio/rest/preview_iam/v1/token.py | 2 +- 16 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 twilio/authStrategy/authStrategy.py create mode 100644 twilio/authStrategy/authType.py create mode 100644 twilio/authStrategy/basicAuthStrategy.py create mode 100644 twilio/authStrategy/noAuthStrategy.py create mode 100644 twilio/authStrategy/tokenAuthStrategy.py create mode 100644 twilio/credential/credentialProvider.py create mode 100644 twilio/credential/orgsCredentialProvider.py diff --git a/twilio/authStrategy/authStrategy.py b/twilio/authStrategy/authStrategy.py new file mode 100644 index 000000000..55b901848 --- /dev/null +++ b/twilio/authStrategy/authStrategy.py @@ -0,0 +1,21 @@ +from twilio.authStrategy.authType import AuthType +from enum import Enum +from abc import abstractmethod + + +class AuthStrategy(object): + def __init__(self, auth_type: AuthType): + self._auth_type = auth_type + + @property + def auth_type(self) -> AuthType: + return self._auth_type + + def get_auth_string(self) -> str: + """Return the authentication string.""" + pass + + @abstractmethod + def requires_authentication(self) -> bool: + """Return True if authentication is required, else False.""" + pass \ No newline at end of file diff --git a/twilio/authStrategy/authType.py b/twilio/authStrategy/authType.py new file mode 100644 index 000000000..fe9f00203 --- /dev/null +++ b/twilio/authStrategy/authType.py @@ -0,0 +1,11 @@ +from enum import Enum + +class AuthType(Enum): + TOKEN = 'token' + NO_AUTH = 'noauth' + BASIC = 'basic' + API_KEY = 'api_key' + CLIENT_CREDENTIALS = 'client_credentials' + + def __str__(self): + return self.value \ No newline at end of file diff --git a/twilio/authStrategy/basicAuthStrategy.py b/twilio/authStrategy/basicAuthStrategy.py new file mode 100644 index 000000000..18784a316 --- /dev/null +++ b/twilio/authStrategy/basicAuthStrategy.py @@ -0,0 +1,17 @@ +import base64 +from enum import Enum + + +class BasicAuthStrategy(AuthStrategy): + def __init__(self, username: str, password: str): + super().__init__(AuthType.BASIC) + self.username = username + self.password = password + + def get_auth_string(self) -> str: + credentials = f"{self.username}:{self.password}" + encoded = base64.b64encode(credentials.encode('ascii')).decode('ascii') + return f"Basic {encoded}" + + def requires_authentication(self) -> bool: + return True \ No newline at end of file diff --git a/twilio/authStrategy/noAuthStrategy.py b/twilio/authStrategy/noAuthStrategy.py new file mode 100644 index 000000000..138195106 --- /dev/null +++ b/twilio/authStrategy/noAuthStrategy.py @@ -0,0 +1,11 @@ +from auth_type import AuthType + +class NoAuthStrategy(AuthStrategy): + def __init__(self): + super().__init__(AuthType.NO_AUTH) + + def get_auth_string(self) -> str: + return "" + + def requires_authentication(self) -> bool: + return False \ No newline at end of file diff --git a/twilio/authStrategy/tokenAuthStrategy.py b/twilio/authStrategy/tokenAuthStrategy.py new file mode 100644 index 000000000..ad6a0bb3c --- /dev/null +++ b/twilio/authStrategy/tokenAuthStrategy.py @@ -0,0 +1,35 @@ +import jwt +import threading +from datetime import datetime, timedelta + +from twilio.authStrategy.authType import AuthType +from twilio.authStrategy.authStrategy import AuthStrategy +from twilio.http.token_manager import TokenManager + + +class TokenAuthStrategy(AuthStrategy): + def __init__(self, token_manager: TokenManager): + super().__init__(AuthType.TOKEN) + self.token_manager = token_manager + self.token = None + self.lock = threading.Lock() + + def get_auth_string(self) -> str: + return f"Bearer {self.token}" + + def requires_authentication(self) -> bool: + return True + + def fetch_token(self): + if self.token is None or self.token == "" or self.is_token_expired(self.token): + with self.lock: + if self.token is None or self.token == "" or self.is_token_expired(self.token): + self.token = self.token_manager.fetch_access_token() + + def is_token_expired(self, token): + decoded_jwt = jwt.decode(token, options={"verify_signature": True}) + expires_at = decoded_jwt.get("exp") + # Add a buffer of 30 seconds + buffer_seconds = 30 + buffer_expires_at = expires_at - buffer_seconds + return buffer_expires_at < datetime.datetime.now().timestamp() \ No newline at end of file diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 1bee0f613..0f2a000d8 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -4,10 +4,11 @@ from urllib.parse import urlparse, urlunparse from twilio import __version__ -from twilio.base.exceptions import TwilioException from twilio.http import HttpClient from twilio.http.http_client import TwilioHttpClient from twilio.http.response import Response +from twilio.authStrategy.authType import AuthType +from twilio.credential.credentialProvider import CredentialProvider class ClientBase(object): @@ -23,6 +24,7 @@ def __init__( environment: Optional[MutableMapping[str, str]] = None, edge: Optional[str] = None, user_agent_extensions: Optional[List[str]] = None, + credential_provider: Optional[CredentialProvider] = None, ): """ Initializes the Twilio Client @@ -35,7 +37,9 @@ def __init__( :param environment: Environment to look for auth details, defaults to os.environ :param edge: Twilio Edge to make requests to, defaults to None :param user_agent_extensions: Additions to the user agent string + :param credential_provider: credential provider for authentication method that needs to be used """ + environment = environment or os.environ self.username = username or environment.get("TWILIO_ACCOUNT_SID") @@ -48,9 +52,8 @@ def __init__( """ :type : str """ self.user_agent_extensions = user_agent_extensions or [] """ :type : list[str] """ - - if not self.username or not self.password: - raise TwilioException("Credentials are required to create a TwilioClient") + self.credential_provider = credential_provider or None + """ :type : CredentialProvider """ self.account_sid = account_sid or self.username """ :type : str """ @@ -69,8 +72,6 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, - domain: Optional[str] = None, ) -> Response: """ Makes a request to the Twilio API using the configured http client @@ -88,20 +89,26 @@ def request( :returns: Response from the Twilio API """ - print('*****') - if not is_oauth: - auth = self.get_auth(auth) headers = self.get_headers(method, headers) + + ##If credential provider is provided by user, get the associated auth strategy + ##Using the auth strategy, fetch the auth string and set it to authorization header + auth_strategy = None ##Initialization + if self.credential_provider: + print(f'Reached here 2 {self.credential_provider}') + auth_strategy = self.credential_provider.to_auth_strategy() + if auth_strategy.auth_type == AuthType.TOKEN: + auth_strategy.fetch_token() + headers["Authorization"] = auth_strategy.get_auth_string() + if auth_strategy.auth_type == AuthType.BASIC: + headers["Authorization"] = auth_strategy.get_auth_string() + else: + auth = self.get_auth(auth) + + print(f'auth2 *** {auth}') + + uri = self.get_hostname(uri) - if is_oauth: - OauthTokenBase = dynamic_import( - "twilio.base.oauth_token_base", "OauthTokenBase" - ) - token = OauthTokenBase().get_oauth_token( - domain, "v1", self.username, self.password - ) - headers["Authorization"] = f"Bearer {token}" - headers.get("Authorization") return self.http_client.request( method, @@ -124,7 +131,6 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Asynchronously makes a request to the Twilio API using the configured http client @@ -146,19 +152,25 @@ async def request_async( raise RuntimeError( "http_client must be asynchronous to support async API requests" ) - if not is_oauth: - auth = self.get_auth(auth) + + headers = self.get_headers(method, headers) - uri = self.get_hostname(uri) - if is_oauth: - OauthTokenBase = dynamic_import( - "twilio.base.oauth_token_base", "OauthTokenBase" - ) - token = OauthTokenBase().get_oauth_token( - domain, "v1", self.username, self.password - ) - headers["Authorization"] = f"Bearer {token}" - headers.get("Authorization") + + ##If credential provider is provided by user, get the associated auth strategy + ##Using the auth strategy, fetch the auth string and set it to authorization header + auth_strategy = None ##Initialization + if self.credential_provider: + print(f'Reached here 1') + auth_strategy = self.credential_provider.to_auth_strategy() + if auth_strategy.auth_type == AuthType.TOKEN: + auth_strategy.fetch_token() + headers["Authorization"] = auth_strategy.get_auth_string() + if auth_strategy.auth_type == AuthType.BASIC: + headers["Authorization"] = auth_strategy.get_auth_string() + else: + auth = self.get_auth(auth) + + print(f'auth2 *** {auth}') return await self.http_client.request( method, diff --git a/twilio/base/domain.py b/twilio/base/domain.py index 72724aa9d..4f8395ddf 100644 --- a/twilio/base/domain.py +++ b/twilio/base/domain.py @@ -32,7 +32,6 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Makes an HTTP request to this domain. @@ -56,8 +55,6 @@ def request( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, - domain=self.base_url, ) async def request_async( @@ -70,7 +67,6 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Makes an asynchronous HTTP request to this domain. @@ -94,5 +90,4 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) diff --git a/twilio/base/exceptions.py b/twilio/base/exceptions.py index 05a7c96e8..9adb4a0d8 100644 --- a/twilio/base/exceptions.py +++ b/twilio/base/exceptions.py @@ -63,7 +63,6 @@ def get_uri(code: int) -> str: red_error=red("HTTP Error"), request_was=white("Your request was:"), http_line=teal("%s %s" % (self.method, self.uri, self.data, self.uri)), - http_line=teal("%s %s" % (self.data, self.headers)), twilio_returned=white("Twilio returned the following information:"), message=blue(str(self.msg)), ) diff --git a/twilio/base/version.py b/twilio/base/version.py index 68cf5ca30..c026f5dbf 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -39,7 +39,6 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Make an HTTP request. @@ -54,7 +53,6 @@ def request( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) async def request_async( @@ -67,7 +65,6 @@ async def request_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Make an asynchronous HTTP request @@ -82,7 +79,6 @@ async def request_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) @classmethod @@ -127,7 +123,6 @@ def fetch( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Fetch a resource instance. @@ -141,7 +136,6 @@ def fetch( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) return self._parse_fetch(method, uri, response) @@ -156,7 +150,6 @@ async def fetch_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Asynchronously fetch a resource instance. @@ -170,7 +163,6 @@ async def fetch_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) return self._parse_fetch(method, uri, response) @@ -194,7 +186,6 @@ def update( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Update a resource instance. @@ -222,7 +213,6 @@ async def update_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Asynchronously update a resource instance. @@ -236,7 +226,6 @@ async def update_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) return self._parse_update(method, uri, response) @@ -260,7 +249,6 @@ def delete( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> bool: """ Delete a resource. @@ -288,7 +276,6 @@ async def delete_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> bool: """ Asynchronously delete a resource. @@ -302,7 +289,6 @@ async def delete_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) return self._parse_delete(method, uri, response) @@ -361,7 +347,6 @@ async def page_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Makes an asynchronous HTTP request. @@ -375,7 +360,6 @@ async def page_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) def stream( diff --git a/twilio/credential/credentialProvider.py b/twilio/credential/credentialProvider.py new file mode 100644 index 000000000..2fc103f02 --- /dev/null +++ b/twilio/credential/credentialProvider.py @@ -0,0 +1,12 @@ +from twilio.authStrategy.authType import AuthType + +class CredentialProvider: + def __init__(self, auth_type: AuthType): + self._auth_type = auth_type + + @property + def auth_type(self) -> AuthType: + return self._auth_type + + def to_auth_strategy(self): + raise NotImplementedError("Subclasses must implement this method") diff --git a/twilio/credential/orgsCredentialProvider.py b/twilio/credential/orgsCredentialProvider.py new file mode 100644 index 000000000..6668a18d4 --- /dev/null +++ b/twilio/credential/orgsCredentialProvider.py @@ -0,0 +1,26 @@ + + +from twilio.http.orgs_token_manager import OrgTokenManager +from twilio.base.exceptions import TwilioException +from twilio.credential.credentialProvider import CredentialProvider +from twilio.authStrategy.authType import AuthType +from twilio.authStrategy.tokenAuthStrategy import TokenAuthStrategy + + +class OrgsCredentialProvider(CredentialProvider): + def __init__(self, client_id: str, client_secret: str, token_manager=None): + super().__init__(AuthType.CLIENT_CREDENTIALS) + + if client_id is None or client_secret is None: + raise TwilioException("Invalid credentials passed") + + self.grant_type = "client_credentials" + self.client_id = client_id + self.client_secret = client_secret + self.token_manager = token_manager + + def to_auth_strategy(self): + if self.token_manager is None: + self.token_manager = OrgTokenManager(self.grant_type, self.client_id, self.client_secret) + + return TokenAuthStrategy(self.token_manager) diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index c97c6e830..3e3560084 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -8,6 +8,7 @@ from twilio.http import HttpClient from twilio.http.request import Request as TwilioRequest from twilio.http.response import Response +from twilio.authStrategy.authStrategy import AuthStrategy _logger = logging.getLogger("twilio.http_client") @@ -57,7 +58,6 @@ def request( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Response: """ Make an HTTP Request with parameters provided. @@ -79,6 +79,12 @@ def request( elif timeout <= 0: raise ValueError(timeout) + print(f'auth *** {auth}') + + if "Requires-Authentication" in headers: + headers.pop("Requires-Authentication", None) + auth = None + kwargs = { "method": method.upper(), "url": url, @@ -92,7 +98,7 @@ def request( else: kwargs["data"] = data self.log_request(kwargs) - + print(f'kwargs {kwargs}') self._test_only_last_response = None session = self.session or Session() request = Request(**kwargs) @@ -103,7 +109,6 @@ def request( settings = session.merge_environment_settings( prepped_request.url, self.proxy, None, None, None ) - response = session.send( prepped_request, allow_redirects=allow_redirects, diff --git a/twilio/http/orgs_token_manager.py b/twilio/http/orgs_token_manager.py index 1929f21a5..396231b83 100644 --- a/twilio/http/orgs_token_manager.py +++ b/twilio/http/orgs_token_manager.py @@ -1,6 +1,7 @@ from twilio.base.version import Version from twilio.http.token_manager import TokenManager from twilio.rest.preview_iam.v1.token import TokenList +from twilio.rest import Client class OrgTokenManager(TokenManager): @@ -27,10 +28,11 @@ def __init__( self.audience = audience self.refreshToken = refreshToken self.scope = scope + self.client = Client() - def fetch_access_token(self, version: Version): - token_list = TokenList(version) - token_instance = token_list.create( + def fetch_access_token(self): + # token_list = TokenList(version) + token_instance = self.client.preview_iam.v1.token.create( grant_type=self.grant_type, client_id=self.client_id, client_secret=self.client_secret, diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 9c71ce2bb..06c5ef9f3 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -95,6 +95,7 @@ def __init__( environment=None, edge=None, user_agent_extensions=None, + credential_provider=None, ): """ Initializes the Twilio Client @@ -111,6 +112,7 @@ def __init__( :returns: Twilio Client :rtype: twilio.rest.Client """ + super().__init__( username, password, @@ -120,6 +122,7 @@ def __init__( environment, edge, user_agent_extensions, + credential_provider, ) # Domains diff --git a/twilio/rest/preview_iam/organizations/__init__.py b/twilio/rest/preview_iam/organizations/__init__.py index 9cd6cd7a6..6a57e0b45 100644 --- a/twilio/rest/preview_iam/organizations/__init__.py +++ b/twilio/rest/preview_iam/organizations/__init__.py @@ -27,17 +27,23 @@ def __init__(self, domain: Domain): :param domain: The Twilio.preview_iam domain """ - super().__init__(domain, "organizations") + super().__init__(domain, "Organizations") self._accounts: Optional[AccountList] = None self._role_assignments: Optional[RoleAssignmentList] = None - self._users: Optional[UserList] = None + # self._users: Optional[UserList] = None @property def accounts(self) -> AccountList: if self._accounts is None: - self._accounts = AccountList(self) + self._accounts = AccountList(self, organization_sid="OR64adedc0f4dc99b9113305f725677b47") return self._accounts + # @property + # def accounts(self, organization_sid) -> AccountList: + # if self._accounts is None: + # self._accounts = AccountList(self, "OR64adedc0f4dc99b9113305f725677b47") + # return self._accounts + @property def role_assignments(self) -> RoleAssignmentList: if self._role_assignments is None: diff --git a/twilio/rest/preview_iam/v1/token.py b/twilio/rest/preview_iam/v1/token.py index 50e37b9ea..461ee40fe 100644 --- a/twilio/rest/preview_iam/v1/token.py +++ b/twilio/rest/preview_iam/v1/token.py @@ -99,7 +99,7 @@ def create( "scope": scope, } ) - headers = values.of({"Content-Type": "application/x-www-form-urlencoded"}) + headers = values.of({"Content-Type": "application/x-www-form-urlencoded", "Requires-Authentication": "none"}) payload = self._version.create( method="POST", uri=self._uri, data=data, headers=headers ) From 7b07ba7952807b8d80087cb27bf2f4bbaf53ffbb Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 17:58:45 +0530 Subject: [PATCH 09/22] twilio python changes for orgs api uptake --- twilio/authStrategy/basicAuthStrategy.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 twilio/authStrategy/basicAuthStrategy.py diff --git a/twilio/authStrategy/basicAuthStrategy.py b/twilio/authStrategy/basicAuthStrategy.py deleted file mode 100644 index 18784a316..000000000 --- a/twilio/authStrategy/basicAuthStrategy.py +++ /dev/null @@ -1,17 +0,0 @@ -import base64 -from enum import Enum - - -class BasicAuthStrategy(AuthStrategy): - def __init__(self, username: str, password: str): - super().__init__(AuthType.BASIC) - self.username = username - self.password = password - - def get_auth_string(self) -> str: - credentials = f"{self.username}:{self.password}" - encoded = base64.b64encode(credentials.encode('ascii')).decode('ascii') - return f"Basic {encoded}" - - def requires_authentication(self) -> bool: - return True \ No newline at end of file From af11fd29876c70936314917f526203babe8186d6 Mon Sep 17 00:00:00 2001 From: Athira Sabu <102021496+AsabuHere@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:59:13 +0530 Subject: [PATCH 10/22] Update test_cluster.py --- tests/cluster/test_cluster.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/cluster/test_cluster.py b/tests/cluster/test_cluster.py index 76a6f85d0..3b96d1813 100644 --- a/tests/cluster/test_cluster.py +++ b/tests/cluster/test_cluster.py @@ -20,14 +20,6 @@ def setUp(self): self.voice_twiml = VoiceResponse() - def test_token_fetch(self): - token = self.client.preview_iam.token.create( - grant_type = GRANT_TYPE, - client_id = CLIENT_ID, - client_secret = CLIENT_SECRET) - print(f'{token}') - - def test_send_text_message(self): msg = self.client.messages.create( to=self.to_number, from_=self.from_number, body="hello world" From 661785db3b09499392e985bfe15b7249b0485c40 Mon Sep 17 00:00:00 2001 From: Athira Sabu <102021496+AsabuHere@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:59:27 +0530 Subject: [PATCH 11/22] Update test_cluster.py --- tests/cluster/test_cluster.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cluster/test_cluster.py b/tests/cluster/test_cluster.py index 3b96d1813..556fa8da7 100644 --- a/tests/cluster/test_cluster.py +++ b/tests/cluster/test_cluster.py @@ -19,7 +19,6 @@ def setUp(self): ) self.voice_twiml = VoiceResponse() - def test_send_text_message(self): msg = self.client.messages.create( to=self.to_number, from_=self.from_number, body="hello world" From 6a8c2d8b4212cff07c0d12ba2aceb724d043a2b3 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 18:01:16 +0530 Subject: [PATCH 12/22] twilio python changes for orgs api uptake --- twilio/base/client_base.py | 6 ------ twilio/http/http_client.py | 3 --- 2 files changed, 9 deletions(-) diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 0f2a000d8..4a109e2f8 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -95,7 +95,6 @@ def request( ##Using the auth strategy, fetch the auth string and set it to authorization header auth_strategy = None ##Initialization if self.credential_provider: - print(f'Reached here 2 {self.credential_provider}') auth_strategy = self.credential_provider.to_auth_strategy() if auth_strategy.auth_type == AuthType.TOKEN: auth_strategy.fetch_token() @@ -105,8 +104,6 @@ def request( else: auth = self.get_auth(auth) - print(f'auth2 *** {auth}') - uri = self.get_hostname(uri) @@ -160,7 +157,6 @@ async def request_async( ##Using the auth strategy, fetch the auth string and set it to authorization header auth_strategy = None ##Initialization if self.credential_provider: - print(f'Reached here 1') auth_strategy = self.credential_provider.to_auth_strategy() if auth_strategy.auth_type == AuthType.TOKEN: auth_strategy.fetch_token() @@ -170,8 +166,6 @@ async def request_async( else: auth = self.get_auth(auth) - print(f'auth2 *** {auth}') - return await self.http_client.request( method, uri, diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index 3e3560084..de5bdef53 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -79,8 +79,6 @@ def request( elif timeout <= 0: raise ValueError(timeout) - print(f'auth *** {auth}') - if "Requires-Authentication" in headers: headers.pop("Requires-Authentication", None) auth = None @@ -98,7 +96,6 @@ def request( else: kwargs["data"] = data self.log_request(kwargs) - print(f'kwargs {kwargs}') self._test_only_last_response = None session = self.session or Session() request = Request(**kwargs) From 1ba2f9b2b8004559c80b2ebed1b54b31223bf0ed Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 18:13:43 +0530 Subject: [PATCH 13/22] twilio python changes for orgs api uptake --- twilio/base/client_base.py | 11 ------ twilio/base/exceptions.py | 2 +- twilio/base/oauth_token_base.py | 37 ------------------- twilio/base/version.py | 3 -- twilio/http/no_auth_http_client.py | 4 -- twilio/http/orgs_token_manager.py | 1 - twilio/rest/__init__.py | 1 - .../preview_iam/organizations/__init__.py | 7 +--- twilio/twilio_bearer_token_auth.py | 33 ----------------- 9 files changed, 2 insertions(+), 97 deletions(-) delete mode 100644 twilio/base/oauth_token_base.py delete mode 100644 twilio/http/no_auth_http_client.py delete mode 100644 twilio/twilio_bearer_token_auth.py diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 4a109e2f8..5eb4af623 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -99,8 +99,6 @@ def request( if auth_strategy.auth_type == AuthType.TOKEN: auth_strategy.fetch_token() headers["Authorization"] = auth_strategy.get_auth_string() - if auth_strategy.auth_type == AuthType.BASIC: - headers["Authorization"] = auth_strategy.get_auth_string() else: auth = self.get_auth(auth) @@ -161,8 +159,6 @@ async def request_async( if auth_strategy.auth_type == AuthType.TOKEN: auth_strategy.fetch_token() headers["Authorization"] = auth_strategy.get_auth_string() - if auth_strategy.auth_type == AuthType.BASIC: - headers["Authorization"] = auth_strategy.get_auth_string() else: auth = self.get_auth(auth) @@ -262,10 +258,3 @@ def __repr__(self) -> str: :returns: Machine friendly representation """ return "".format(self.account_sid) - - -def dynamic_import(module_name, class_name): - from importlib import import_module - - module = import_module(module_name) - return getattr(module, class_name) diff --git a/twilio/base/exceptions.py b/twilio/base/exceptions.py index 9adb4a0d8..8f3b7cc7a 100644 --- a/twilio/base/exceptions.py +++ b/twilio/base/exceptions.py @@ -62,7 +62,7 @@ def get_uri(code: int) -> str: "\n\n{twilio_returned}\n\n{message}\n".format( red_error=red("HTTP Error"), request_was=white("Your request was:"), - http_line=teal("%s %s" % (self.method, self.uri, self.data, self.uri)), + http_line=teal("%s %s" % (self.method, self.uri)), twilio_returned=white("Twilio returned the following information:"), message=blue(str(self.msg)), ) diff --git a/twilio/base/oauth_token_base.py b/twilio/base/oauth_token_base.py deleted file mode 100644 index f58a143c8..000000000 --- a/twilio/base/oauth_token_base.py +++ /dev/null @@ -1,37 +0,0 @@ -from twilio.http.token_manager_initializer import TokenManagerInitializer - - -# Dynamic import utility function -def dynamic_import(module_name, class_name): - from importlib import import_module - - module = import_module(module_name) - return getattr(module, class_name) - - -class OauthTokenBase: - def get_oauth_token(self, domain: str, version: str, username: str, password: str): - Domain = dynamic_import("twilio.base.domain", "Domain") - Version = dynamic_import("twilio.base.version", "Version") - BearerTokenHTTPClient = dynamic_import( - "twilio.http.bearer_token_http_client", "BearerTokenHTTPClient" - ) - OrgTokenManager = dynamic_import( - "twilio.http.orgs_token_manager", "OrgTokenManager" - ) - Client = dynamic_import("twilio.rest", "Client") - try: - orgs_token_manager = TokenManagerInitializer.get_token_manager() - return BearerTokenHTTPClient(orgs_token_manager).get_access_token( - Version(Domain(Client(username, password), domain), version) - ) - except Exception: - orgs_token_manager = OrgTokenManager( - grant_type="client_credentials", - client_id=username, - client_secret=password, - ) - TokenManagerInitializer().set_token_manager(orgs_token_manager) - return BearerTokenHTTPClient(orgs_token_manager).get_access_token( - Version(Domain(Client(username, password), domain), version) - ) diff --git a/twilio/base/version.py b/twilio/base/version.py index c026f5dbf..4d7ab803c 100644 --- a/twilio/base/version.py +++ b/twilio/base/version.py @@ -447,7 +447,6 @@ def create( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Create a resource instance. @@ -474,7 +473,6 @@ async def create_async( auth: Optional[Tuple[str, str]] = None, timeout: Optional[float] = None, allow_redirects: bool = False, - is_oauth: bool = False, ) -> Any: """ Asynchronously create a resource instance. @@ -488,6 +486,5 @@ async def create_async( auth=auth, timeout=timeout, allow_redirects=allow_redirects, - is_oauth=is_oauth, ) return self._parse_create(method, uri, response) diff --git a/twilio/http/no_auth_http_client.py b/twilio/http/no_auth_http_client.py deleted file mode 100644 index daa5e5cdc..000000000 --- a/twilio/http/no_auth_http_client.py +++ /dev/null @@ -1,4 +0,0 @@ -class NoAuthHTTPClient: - def get_headers(self): - headers = {} - return headers diff --git a/twilio/http/orgs_token_manager.py b/twilio/http/orgs_token_manager.py index 396231b83..a232d4f51 100644 --- a/twilio/http/orgs_token_manager.py +++ b/twilio/http/orgs_token_manager.py @@ -31,7 +31,6 @@ def __init__( self.client = Client() def fetch_access_token(self): - # token_list = TokenList(version) token_instance = self.client.preview_iam.v1.token.create( grant_type=self.grant_type, client_id=self.client_id, diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 06c5ef9f3..92d729d20 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -112,7 +112,6 @@ def __init__( :returns: Twilio Client :rtype: twilio.rest.Client """ - super().__init__( username, password, diff --git a/twilio/rest/preview_iam/organizations/__init__.py b/twilio/rest/preview_iam/organizations/__init__.py index 6a57e0b45..70e1772ef 100644 --- a/twilio/rest/preview_iam/organizations/__init__.py +++ b/twilio/rest/preview_iam/organizations/__init__.py @@ -35,14 +35,9 @@ def __init__(self, domain: Domain): @property def accounts(self) -> AccountList: if self._accounts is None: - self._accounts = AccountList(self, organization_sid="OR64adedc0f4dc99b9113305f725677b47") + self._accounts = AccountList(self) return self._accounts - # @property - # def accounts(self, organization_sid) -> AccountList: - # if self._accounts is None: - # self._accounts = AccountList(self, "OR64adedc0f4dc99b9113305f725677b47") - # return self._accounts @property def role_assignments(self) -> RoleAssignmentList: diff --git a/twilio/twilio_bearer_token_auth.py b/twilio/twilio_bearer_token_auth.py deleted file mode 100644 index ffc8814cd..000000000 --- a/twilio/twilio_bearer_token_auth.py +++ /dev/null @@ -1,33 +0,0 @@ -from threading import Lock - - -class BearerTokenTwilioRestClient: - pass - - -class TwilioBearerTokenAuth: - _lock = Lock() - access_token = None - rest_client = None - user_agent_extensions = None - region = None - edge = None - - @classmethod - def init(cls, access_token): - with cls._lock: - if not access_token: - raise ValueError("Access Token cannot be null or Empty") - if access_token != cls.access_token: - cls.access_token = None - cls.access_token = access_token - - @classmethod - def get_access_token(cls): - with cls._lock: - return cls.access_token - - @classmethod - def get_header_param(cls): - with cls._lock: - return {"Authorization": "Bearer {token}".format(token=cls.access_token)} From 98708f08a9389bea72cb199f150b5b9daf94ae17 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 18:47:49 +0530 Subject: [PATCH 14/22] twilio python changes for orgs api uptake --- twilio/http/bearer_token_http_client.py | 30 ------------------------- twilio/http/http_client.py | 7 +++--- 2 files changed, 4 insertions(+), 33 deletions(-) delete mode 100644 twilio/http/bearer_token_http_client.py diff --git a/twilio/http/bearer_token_http_client.py b/twilio/http/bearer_token_http_client.py deleted file mode 100644 index eb5758db9..000000000 --- a/twilio/http/bearer_token_http_client.py +++ /dev/null @@ -1,30 +0,0 @@ -import datetime -import jwt - -from twilio.base.version import Version -from twilio.http.token_manager import TokenManager -from twilio.twilio_bearer_token_auth import TwilioBearerTokenAuth - - -class BearerTokenHTTPClient: - def __init__(self, orgs_token_manager: TokenManager): - self.orgs_token_manager = orgs_token_manager - - def get_access_token(self, version: Version): - if TwilioBearerTokenAuth.get_access_token() is None or self.is_token_expired( - TwilioBearerTokenAuth.get_access_token() - ): - access_token = self.orgs_token_manager.fetch_access_token(version) - TwilioBearerTokenAuth.init(access_token) - else: - access_token = TwilioBearerTokenAuth.get_access_token() - - return access_token - - def is_token_expired(self, token): - decoded_jwt = jwt.decode(token, options={"verify_signature": True}) - expires_at = decoded_jwt.get("exp") - # Add a buffer of 30 seconds - buffer_seconds = 30 - buffer_expires_at = expires_at - buffer_seconds - return buffer_expires_at < datetime.datetime.now().timestamp() diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index de5bdef53..83c0fd13d 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -79,9 +79,10 @@ def request( elif timeout <= 0: raise ValueError(timeout) - if "Requires-Authentication" in headers: - headers.pop("Requires-Authentication", None) - auth = None + if headers: + if "Requires-Authentication" in headers: + headers.pop("Requires-Authentication", None) + auth = None kwargs = { "method": method.upper(), From d78d5d59effe1f043bfeec2562936678780bf532 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 26 Sep 2024 18:51:25 +0530 Subject: [PATCH 15/22] twilio python changes for orgs api uptake --- twilio/http/http_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index 83c0fd13d..0e3e7638f 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -8,7 +8,6 @@ from twilio.http import HttpClient from twilio.http.request import Request as TwilioRequest from twilio.http.response import Response -from twilio.authStrategy.authStrategy import AuthStrategy _logger = logging.getLogger("twilio.http_client") From bc777701090ff4f4a0d8f80c77fbd832ad3e4ab4 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Fri, 27 Sep 2024 13:16:53 +0530 Subject: [PATCH 16/22] twilio python changes for orgs api uptake --- twilio/authStrategy/__init__.py | 0 twilio/authStrategy/authStrategy.py | 1 + 2 files changed, 1 insertion(+) create mode 100644 twilio/authStrategy/__init__.py diff --git a/twilio/authStrategy/__init__.py b/twilio/authStrategy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/twilio/authStrategy/authStrategy.py b/twilio/authStrategy/authStrategy.py index 55b901848..901a6b4b7 100644 --- a/twilio/authStrategy/authStrategy.py +++ b/twilio/authStrategy/authStrategy.py @@ -11,6 +11,7 @@ def __init__(self, auth_type: AuthType): def auth_type(self) -> AuthType: return self._auth_type + @abstractmethod def get_auth_string(self) -> str: """Return the authentication string.""" pass From 0211f23f37c55e050960e607eb6e503ca9ee5607 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Fri, 27 Sep 2024 13:20:20 +0530 Subject: [PATCH 17/22] twilio python changes for orgs api uptake --- twilio/credential/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 twilio/credential/__init__.py diff --git a/twilio/credential/__init__.py b/twilio/credential/__init__.py new file mode 100644 index 000000000..e69de29bb From 27dec325b9a71e8d341652faa9396dbaa56b95d9 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Fri, 27 Sep 2024 13:52:10 +0530 Subject: [PATCH 18/22] twilio python changes for orgs api uptake --- twilio/authStrategy/tokenAuthStrategy.py | 6 +++++- twilio/base/client_base.py | 4 +--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/twilio/authStrategy/tokenAuthStrategy.py b/twilio/authStrategy/tokenAuthStrategy.py index ad6a0bb3c..ac8acafcb 100644 --- a/twilio/authStrategy/tokenAuthStrategy.py +++ b/twilio/authStrategy/tokenAuthStrategy.py @@ -15,19 +15,23 @@ def __init__(self, token_manager: TokenManager): self.lock = threading.Lock() def get_auth_string(self) -> str: + if self.token is None: + self.fetch_token() return f"Bearer {self.token}" def requires_authentication(self) -> bool: return True def fetch_token(self): + print(f'token is fetch_token {self.token}') if self.token is None or self.token == "" or self.is_token_expired(self.token): with self.lock: if self.token is None or self.token == "" or self.is_token_expired(self.token): self.token = self.token_manager.fetch_access_token() def is_token_expired(self, token): - decoded_jwt = jwt.decode(token, options={"verify_signature": True}) + print(f'token is {token}') + decoded_jwt = jwt.decode(token, options={"verify_signature": True}, algorithms=["RS256"]) expires_at = decoded_jwt.get("exp") # Add a buffer of 30 seconds buffer_seconds = 30 diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 5eb4af623..2d18e8899 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -96,9 +96,7 @@ def request( auth_strategy = None ##Initialization if self.credential_provider: auth_strategy = self.credential_provider.to_auth_strategy() - if auth_strategy.auth_type == AuthType.TOKEN: - auth_strategy.fetch_token() - headers["Authorization"] = auth_strategy.get_auth_string() + headers["Authorization"] = auth_strategy.get_auth_string() else: auth = self.get_auth(auth) From 29596894ac20277f42afa30247a7cbe3f27c5eca Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Fri, 27 Sep 2024 13:58:34 +0530 Subject: [PATCH 19/22] twilio python changes for orgs api uptake --- twilio/rest/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index e2b644489..458168fa2 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -136,6 +136,7 @@ def __init__( self._events: Optional["Events"] = None self._flex_api: Optional["FlexApi"] = None self._frontline_api: Optional["FrontlineApi"] = None + self._preview_iam: Optional["PreviewIam"] = None self._insights: Optional["Insights"] = None self._intelligence: Optional["Intelligence"] = None self._ip_messaging: Optional["IpMessaging"] = None @@ -398,6 +399,19 @@ def microvisor(self) -> "Microvisor": self._microvisor = Microvisor(self) return self._microvisor + @property + def preview_iam(self) -> "PreviewIam": + """ + Access the PreviewIam Twilio Domain + + :returns: PreviewIam Twilio Domain + """ + if self._preview_iam is None: + from twilio.rest.preview_iam import PreviewIam + + self._preview_iam = PreviewIam(self) + return self._preview_iam + @property def monitor(self) -> "Monitor": """ From b973065d28328adebdccb71c63a91dd435b08deb Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 1 Oct 2024 22:36:51 +0530 Subject: [PATCH 20/22] Uptake of review comments --- .../{authStrategy => auth_strategy}/__init__.py | 0 .../auth_strategy.py} | 2 +- .../authType.py => auth_strategy/auth_type.py} | 2 +- .../no_auth_strategy.py} | 0 .../token_auth_strategy.py} | 11 +++++++---- twilio/base/client_base.py | 12 +++++------- ...dentialProvider.py => credential_provider.py} | 2 +- ...alProvider.py => orgs_credential_provider.py} | 6 +++--- twilio/http/token_manager_initializer.py | 16 ---------------- .../rest/preview_iam/organizations/__init__.py | 2 +- 10 files changed, 19 insertions(+), 34 deletions(-) rename twilio/{authStrategy => auth_strategy}/__init__.py (100%) rename twilio/{authStrategy/authStrategy.py => auth_strategy/auth_strategy.py} (90%) rename twilio/{authStrategy/authType.py => auth_strategy/auth_type.py} (86%) rename twilio/{authStrategy/noAuthStrategy.py => auth_strategy/no_auth_strategy.py} (100%) rename twilio/{authStrategy/tokenAuthStrategy.py => auth_strategy/token_auth_strategy.py} (78%) rename twilio/credential/{credentialProvider.py => credential_provider.py} (85%) rename twilio/credential/{orgsCredentialProvider.py => orgs_credential_provider.py} (80%) delete mode 100644 twilio/http/token_manager_initializer.py diff --git a/twilio/authStrategy/__init__.py b/twilio/auth_strategy/__init__.py similarity index 100% rename from twilio/authStrategy/__init__.py rename to twilio/auth_strategy/__init__.py diff --git a/twilio/authStrategy/authStrategy.py b/twilio/auth_strategy/auth_strategy.py similarity index 90% rename from twilio/authStrategy/authStrategy.py rename to twilio/auth_strategy/auth_strategy.py index 901a6b4b7..63107ef97 100644 --- a/twilio/authStrategy/authStrategy.py +++ b/twilio/auth_strategy/auth_strategy.py @@ -1,4 +1,4 @@ -from twilio.authStrategy.authType import AuthType +from twilio.auth_strategy.auth_type import AuthType from enum import Enum from abc import abstractmethod diff --git a/twilio/authStrategy/authType.py b/twilio/auth_strategy/auth_type.py similarity index 86% rename from twilio/authStrategy/authType.py rename to twilio/auth_strategy/auth_type.py index fe9f00203..83653b756 100644 --- a/twilio/authStrategy/authType.py +++ b/twilio/auth_strategy/auth_type.py @@ -1,7 +1,7 @@ from enum import Enum class AuthType(Enum): - TOKEN = 'token' + ORGS_TOKEN = 'orgs_stoken' NO_AUTH = 'noauth' BASIC = 'basic' API_KEY = 'api_key' diff --git a/twilio/authStrategy/noAuthStrategy.py b/twilio/auth_strategy/no_auth_strategy.py similarity index 100% rename from twilio/authStrategy/noAuthStrategy.py rename to twilio/auth_strategy/no_auth_strategy.py diff --git a/twilio/authStrategy/tokenAuthStrategy.py b/twilio/auth_strategy/token_auth_strategy.py similarity index 78% rename from twilio/authStrategy/tokenAuthStrategy.py rename to twilio/auth_strategy/token_auth_strategy.py index ac8acafcb..88b0d787f 100644 --- a/twilio/authStrategy/tokenAuthStrategy.py +++ b/twilio/auth_strategy/token_auth_strategy.py @@ -1,18 +1,21 @@ import jwt import threading +import logging from datetime import datetime, timedelta -from twilio.authStrategy.authType import AuthType -from twilio.authStrategy.authStrategy import AuthStrategy +from twilio.auth_strategy.auth_type import AuthType +from twilio.auth_strategy.auth_strategy import AuthStrategy from twilio.http.token_manager import TokenManager class TokenAuthStrategy(AuthStrategy): def __init__(self, token_manager: TokenManager): - super().__init__(AuthType.TOKEN) + super().__init__(AuthType.ORGS_TOKEN) self.token_manager = token_manager self.token = None self.lock = threading.Lock() + logging.basicConfig(level=logging.INFO) + self.logger = logging.getLogger(__name__) def get_auth_string(self) -> str: if self.token is None: @@ -23,7 +26,7 @@ def requires_authentication(self) -> bool: return True def fetch_token(self): - print(f'token is fetch_token {self.token}') + self.logger.info("New token fetched for accessing organization API") if self.token is None or self.token == "" or self.is_token_expired(self.token): with self.lock: if self.token is None or self.token == "" or self.is_token_expired(self.token): diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 2d18e8899..1fdc689ea 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -7,8 +7,8 @@ from twilio.http import HttpClient from twilio.http.http_client import TwilioHttpClient from twilio.http.response import Response -from twilio.authStrategy.authType import AuthType -from twilio.credential.credentialProvider import CredentialProvider +from twilio.auth_strategy.auth_type import AuthType +from twilio.credential.credential_provider import CredentialProvider class ClientBase(object): @@ -93,7 +93,6 @@ def request( ##If credential provider is provided by user, get the associated auth strategy ##Using the auth strategy, fetch the auth string and set it to authorization header - auth_strategy = None ##Initialization if self.credential_provider: auth_strategy = self.credential_provider.to_auth_strategy() headers["Authorization"] = auth_strategy.get_auth_string() @@ -151,15 +150,14 @@ async def request_async( ##If credential provider is provided by user, get the associated auth strategy ##Using the auth strategy, fetch the auth string and set it to authorization header - auth_strategy = None ##Initialization if self.credential_provider: auth_strategy = self.credential_provider.to_auth_strategy() - if auth_strategy.auth_type == AuthType.TOKEN: - auth_strategy.fetch_token() - headers["Authorization"] = auth_strategy.get_auth_string() + headers["Authorization"] = auth_strategy.get_auth_string() else: auth = self.get_auth(auth) + uri = self.get_hostname(uri) + return await self.http_client.request( method, uri, diff --git a/twilio/credential/credentialProvider.py b/twilio/credential/credential_provider.py similarity index 85% rename from twilio/credential/credentialProvider.py rename to twilio/credential/credential_provider.py index 2fc103f02..27e6a7eb4 100644 --- a/twilio/credential/credentialProvider.py +++ b/twilio/credential/credential_provider.py @@ -1,4 +1,4 @@ -from twilio.authStrategy.authType import AuthType +from twilio.auth_strategy.auth_type import AuthType class CredentialProvider: def __init__(self, auth_type: AuthType): diff --git a/twilio/credential/orgsCredentialProvider.py b/twilio/credential/orgs_credential_provider.py similarity index 80% rename from twilio/credential/orgsCredentialProvider.py rename to twilio/credential/orgs_credential_provider.py index 6668a18d4..10d83d32c 100644 --- a/twilio/credential/orgsCredentialProvider.py +++ b/twilio/credential/orgs_credential_provider.py @@ -2,9 +2,9 @@ from twilio.http.orgs_token_manager import OrgTokenManager from twilio.base.exceptions import TwilioException -from twilio.credential.credentialProvider import CredentialProvider -from twilio.authStrategy.authType import AuthType -from twilio.authStrategy.tokenAuthStrategy import TokenAuthStrategy +from twilio.credential.credential_provider import CredentialProvider +from twilio.auth_strategy.auth_type import AuthType +from twilio.auth_strategy.token_auth_strategy import TokenAuthStrategy class OrgsCredentialProvider(CredentialProvider): diff --git a/twilio/http/token_manager_initializer.py b/twilio/http/token_manager_initializer.py deleted file mode 100644 index 36ee83f1a..000000000 --- a/twilio/http/token_manager_initializer.py +++ /dev/null @@ -1,16 +0,0 @@ -from twilio.http.token_manager import TokenManager - - -class TokenManagerInitializer: - - org_token_manager = None - - @classmethod - def set_token_manager(cls, token_manager: TokenManager): - cls.org_token_manager = token_manager - - @classmethod - def get_token_manager(cls): - if cls.org_token_manager is None: - raise Exception("Token Manager not initialized") - return cls.org_token_manager diff --git a/twilio/rest/preview_iam/organizations/__init__.py b/twilio/rest/preview_iam/organizations/__init__.py index 70e1772ef..0dcd28734 100644 --- a/twilio/rest/preview_iam/organizations/__init__.py +++ b/twilio/rest/preview_iam/organizations/__init__.py @@ -35,7 +35,7 @@ def __init__(self, domain: Domain): @property def accounts(self) -> AccountList: if self._accounts is None: - self._accounts = AccountList(self) + self._accounts = AccountList(self, "OR64adedc0f4dc99b9113305f725677b47") return self._accounts From ceebd46e29f3b4fde594630f294a46c4d9bcec0d Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Tue, 1 Oct 2024 22:38:41 +0530 Subject: [PATCH 21/22] modified error messages --- twilio/credential/orgs_credential_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/credential/orgs_credential_provider.py b/twilio/credential/orgs_credential_provider.py index 10d83d32c..4e58271a9 100644 --- a/twilio/credential/orgs_credential_provider.py +++ b/twilio/credential/orgs_credential_provider.py @@ -12,7 +12,7 @@ def __init__(self, client_id: str, client_secret: str, token_manager=None): super().__init__(AuthType.CLIENT_CREDENTIALS) if client_id is None or client_secret is None: - raise TwilioException("Invalid credentials passed") + raise TwilioException("Client id and Client secret are mandatory") self.grant_type = "client_credentials" self.client_id = client_id From 35b5015909953ee62871e8ce03036fae647ee4df Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Sun, 6 Oct 2024 14:11:11 +0530 Subject: [PATCH 22/22] Uptake of review comments --- twilio/auth_strategy/token_auth_strategy.py | 27 ++++++++++++------- twilio/base/client_base.py | 9 +++++-- twilio/credential/orgs_credential_provider.py | 6 +++-- twilio/http/http_client.py | 6 +---- twilio/rest/__init__.py | 1 + 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/twilio/auth_strategy/token_auth_strategy.py b/twilio/auth_strategy/token_auth_strategy.py index 88b0d787f..a21ea44be 100644 --- a/twilio/auth_strategy/token_auth_strategy.py +++ b/twilio/auth_strategy/token_auth_strategy.py @@ -18,8 +18,7 @@ def __init__(self, token_manager: TokenManager): self.logger = logging.getLogger(__name__) def get_auth_string(self) -> str: - if self.token is None: - self.fetch_token() + self.fetch_token() return f"Bearer {self.token}" def requires_authentication(self) -> bool: @@ -28,15 +27,23 @@ def requires_authentication(self) -> bool: def fetch_token(self): self.logger.info("New token fetched for accessing organization API") if self.token is None or self.token == "" or self.is_token_expired(self.token): - with self.lock: + with self.lock: if self.token is None or self.token == "" or self.is_token_expired(self.token): self.token = self.token_manager.fetch_access_token() def is_token_expired(self, token): - print(f'token is {token}') - decoded_jwt = jwt.decode(token, options={"verify_signature": True}, algorithms=["RS256"]) - expires_at = decoded_jwt.get("exp") - # Add a buffer of 30 seconds - buffer_seconds = 30 - buffer_expires_at = expires_at - buffer_seconds - return buffer_expires_at < datetime.datetime.now().timestamp() \ No newline at end of file + try: + decoded = jwt.decode(token, options={"verify_signature": False}) + exp = decoded.get('exp') + + if exp is None: + return True # No expiration time present, consider it expired + + # Check if the expiration time has passed + return datetime.fromtimestamp(exp) < datetime.utcnow() + + except jwt.DecodeError: + return True # Token is invalid + except Exception as e: + print(f"An error occurred: {e}") + return True \ No newline at end of file diff --git a/twilio/base/client_base.py b/twilio/base/client_base.py index 1fdc689ea..8526bdd33 100644 --- a/twilio/base/client_base.py +++ b/twilio/base/client_base.py @@ -96,8 +96,10 @@ def request( if self.credential_provider: auth_strategy = self.credential_provider.to_auth_strategy() headers["Authorization"] = auth_strategy.get_auth_string() - else: + elif self.username is not None and self.password is not None: auth = self.get_auth(auth) + else: + auth = None uri = self.get_hostname(uri) @@ -150,11 +152,14 @@ async def request_async( ##If credential provider is provided by user, get the associated auth strategy ##Using the auth strategy, fetch the auth string and set it to authorization header + if self.credential_provider: auth_strategy = self.credential_provider.to_auth_strategy() headers["Authorization"] = auth_strategy.get_auth_string() - else: + elif self.username is not None and self.password is not None: auth = self.get_auth(auth) + else: + auth = None uri = self.get_hostname(uri) diff --git a/twilio/credential/orgs_credential_provider.py b/twilio/credential/orgs_credential_provider.py index 4e58271a9..6ec31441e 100644 --- a/twilio/credential/orgs_credential_provider.py +++ b/twilio/credential/orgs_credential_provider.py @@ -18,9 +18,11 @@ def __init__(self, client_id: str, client_secret: str, token_manager=None): self.client_id = client_id self.client_secret = client_secret self.token_manager = token_manager + self.auth_strategy = None def to_auth_strategy(self): if self.token_manager is None: self.token_manager = OrgTokenManager(self.grant_type, self.client_id, self.client_secret) - - return TokenAuthStrategy(self.token_manager) + if self.auth_strategy is None: + self.auth_strategy = TokenAuthStrategy(self.token_manager) + return self.auth_strategy diff --git a/twilio/http/http_client.py b/twilio/http/http_client.py index 0e3e7638f..27617fb7a 100644 --- a/twilio/http/http_client.py +++ b/twilio/http/http_client.py @@ -78,11 +78,6 @@ def request( elif timeout <= 0: raise ValueError(timeout) - if headers: - if "Requires-Authentication" in headers: - headers.pop("Requires-Authentication", None) - auth = None - kwargs = { "method": method.upper(), "url": url, @@ -96,6 +91,7 @@ def request( else: kwargs["data"] = data self.log_request(kwargs) + print(f'args : {kwargs}') self._test_only_last_response = None session = self.session or Session() request = Request(**kwargs) diff --git a/twilio/rest/__init__.py b/twilio/rest/__init__.py index 458168fa2..7874236a2 100644 --- a/twilio/rest/__init__.py +++ b/twilio/rest/__init__.py @@ -136,6 +136,7 @@ def __init__( self._events: Optional["Events"] = None self._flex_api: Optional["FlexApi"] = None self._frontline_api: Optional["FrontlineApi"] = None + self._iam: Optional["Iam"] = None self._preview_iam: Optional["PreviewIam"] = None self._insights: Optional["Insights"] = None self._intelligence: Optional["Intelligence"] = None