Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python changes for orgs api uptake #812

Open
wants to merge 39 commits into
base: 10.0.0-rc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
dd72ebe
chore: oauth sdk implementation (#804)
manisha1997 Jul 22, 2024
0cd40b5
chore: update intersphinx_mapping (#807)
tiwarishubham635 Aug 2, 2024
fdd6edc
chore: preview iam removal (#808)
manisha1997 Aug 12, 2024
2b875ec
chore: add license identifier to project metadata (#810)
mschoettle Aug 23, 2024
f4e7765
[Librarian] Regenerated @ c8ce9820730ef3b2a48912311d45afb8c26b9ee7 2e…
twilio-dx Aug 26, 2024
e848451
Release 9.2.4
twilio-dx Aug 26, 2024
ba7da14
[Librarian] Regenerated @ 78bf2bbef74e4846ca8353fbdee038a6b8080c59 22…
twilio-dx Sep 5, 2024
74b506e
Release 9.3.0
twilio-dx Sep 5, 2024
3e246e4
Python Orgs Api Changes
AsabuHere Sep 17, 2024
8395487
removing unwanted logs
AsabuHere Sep 17, 2024
bc5c16b
removing unwanted logs
AsabuHere Sep 17, 2024
a66f9e9
removing unwanted logs
AsabuHere Sep 17, 2024
b5a6490
removing unwanted logs
AsabuHere Sep 17, 2024
fac26ee
Fixing token fetch flow
AsabuHere Sep 17, 2024
3eacd66
chore: add static init file to iam domain (#813)
tiwarishubham635 Sep 18, 2024
e595e28
[Librarian] Regenerated @ 1b6718f23da76f150eac392860c66a26de9af713 ce…
twilio-dx Sep 18, 2024
4168b09
Release 9.3.1
twilio-dx Sep 18, 2024
75e8b14
[Librarian] Regenerated @ 08245333f4a8c9235d547b189cd9c422f73e0e7e 7b…
twilio-dx Sep 25, 2024
0f81bbb
Release 9.3.2
twilio-dx Sep 25, 2024
15e15c0
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
7b07ba7
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
af11fd2
Update test_cluster.py
AsabuHere Sep 26, 2024
661785d
Update test_cluster.py
AsabuHere Sep 26, 2024
6a8c2d8
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
1ba2f9b
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
98708f0
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
d78d5d5
twilio python changes for orgs api uptake
AsabuHere Sep 26, 2024
7bdf1b5
Merge branch 'main' into asabu_Python_changes
AsabuHere Sep 26, 2024
bc77770
twilio python changes for orgs api uptake
AsabuHere Sep 27, 2024
0211f23
twilio python changes for orgs api uptake
AsabuHere Sep 27, 2024
27dec32
twilio python changes for orgs api uptake
AsabuHere Sep 27, 2024
2959689
twilio python changes for orgs api uptake
AsabuHere Sep 27, 2024
b973065
Uptake of review comments
AsabuHere Oct 1, 2024
ceebd46
modified error messages
AsabuHere Oct 1, 2024
7fc68cb
chore: add assistants init files (#816)
tiwarishubham635 Oct 3, 2024
726c36a
[Librarian] Regenerated @ 021bab52f93b55d7a5786bd27716bf3a0be2d7b9 aa…
twilio-dx Oct 3, 2024
69b9ae8
Release 9.3.3
twilio-dx Oct 3, 2024
35b5015
Uptake of review comments
AsabuHere Oct 6, 2024
76fecab
Merge branch 'main' into asabu_Python_changes
AsabuHere Oct 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,99 @@ twilio-python Changelog

Here you can see the full list of changes between each twilio-python release.

[2024-10-03] Version 9.3.3
--------------------------
**Library - Chore**
- [PR #816](https://github.com/twilio/twilio-python/pull/816): add assistants init files. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)!

**Messaging**
- Add A2P external campaign CnpMigration flag

**Numbers**
- Add address sid to portability API

**Verify**
- Add `SnaClientToken` optional parameter on Verification check.
- Add `EnableSnaClientToken` optional parameter for Verification creation.


[2024-09-25] Version 9.3.2
--------------------------
**Accounts**
- Update docs and mounts.
- Change library visibility to public
- Enable consent and contact bulk upsert APIs in prod.

**Serverless**
- Add is_plugin parameter in deployments api to check if it is plugins deployment


[2024-09-18] Version 9.3.1
--------------------------
**Library - Chore**
- [PR #813](https://github.com/twilio/twilio-python/pull/813): add static init file to iam domain. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)!

**Intelligence**
- Remove public from operator_type
- Update operator_type to include general-availablity and deprecated

**Numbers**
- Remove beta flag for bundle clone API


[2024-09-05] Version 9.3.0
--------------------------
**Iam**
- updated library_visibility public for new public apikeys

**Numbers**
- Add new field in Error Codes for Regulatory Compliance.
- Change typing of Port In Request date_created field to date_time instead of date **(breaking change)**


[2024-08-26] Version 9.2.4
--------------------------
**Library - Chore**
- [PR #810](https://github.com/twilio/twilio-python/pull/810): add license identifier to project metadata. Thanks to [@mschoettle](https://github.com/mschoettle)!
- [PR #808](https://github.com/twilio/twilio-python/pull/808): preview iam removal. Thanks to [@manisha1997](https://github.com/manisha1997)!
- [PR #807](https://github.com/twilio/twilio-python/pull/807): update intersphinx_mapping. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)!
- [PR #804](https://github.com/twilio/twilio-python/pull/804): add init file. Thanks to [@manisha1997](https://github.com/manisha1997)!

**Api**
- Update documentation of `error_code` and `error_message` on the Message resource.
- Remove generic parameters from `transcription` resource
- Added public documentation for Payload Data retrieval API

**Flex**
- Adding update Flex User api

**Insights**
- Added 'branded', 'business_profile' and 'voice_integrity' fields in List Call Summary

**Intelligence**
- Add `words` array information to the Sentences v2 entity.
- Add `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`, and `X-Rate-Limit-Config` headers for Operator Results.
- Change the path parameter when fetching an `/OperatorType/{}` from `sid<EY>` to `string` to support searching by SID or by name
- Add `X-Rate-Limit-Limit`, `X-Rate-Limit-Remaining`, and `X-Rate-Limit-Config` headers for Transcript and Service endpoints.

**Messaging**
- Adds two new channel senders api to add/remove channel senders to/from a messaging service
- Extend ERC api to accept an optional attribute in request body to indicate CNP migration for an ERC

**Numbers**
- Modify visibility to public in bundle clone API
- Add `port_date` field to Port In Request and Port In Phone Numbers Fetch APIs
- Change properties docs for port in phone numbers api
- Add is_test body param to the Bundle Create API
- Change properties docs for port in api

**Trusthub**
- Add new field in themeSetId in compliance_inquiry.

**Verify**
- Update `custom_code_enabled` description on verification docs


[2024-07-02] Version 9.2.3
--------------------------
**Intelligence**
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,4 @@
# -- Options for intersphinx extension ---------------------------------------

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ universal = 1

[metadata]
description-file = README.md
license = MIT

[flake8]
exclude = ./twilio/rest,./twilio/twiml,./tests/integration
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

setup(
name="twilio",
version="9.2.3",
version="9.3.3",
description="Twilio API client and TwiML generator",
author="Twilio",
help_center="https://www.twilio.com/help/contact",
Expand Down
2 changes: 1 addition & 1 deletion twilio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version_info__ = ("9", "2", "3")
__version_info__ = ("9", "3", "3")
__version__ = ".".join(__version_info__)
Empty file.
22 changes: 22 additions & 0 deletions twilio/auth_strategy/auth_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from twilio.auth_strategy.auth_type 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

@abstractmethod
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
11 changes: 11 additions & 0 deletions twilio/auth_strategy/auth_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from enum import Enum

class AuthType(Enum):
ORGS_TOKEN = 'orgs_stoken'
NO_AUTH = 'noauth'
BASIC = 'basic'
API_KEY = 'api_key'
CLIENT_CREDENTIALS = 'client_credentials'

def __str__(self):
return self.value
11 changes: 11 additions & 0 deletions twilio/auth_strategy/no_auth_strategy.py
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions twilio/auth_strategy/token_auth_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import jwt
import threading
import logging
from datetime import datetime, timedelta

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.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:
self.fetch_token()
return f"Bearer {self.token}"

def requires_authentication(self) -> bool:
return True

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:
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):
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 failure

Code scanning / SonarCloud

JWT should be signed and verified

<!--SONAR_ISSUE_KEY:AZJhAlYCyje6SmAfcspM-->Don't use a JWT token without verifying its signature. <p>See more on <a href="https://sonarcloud.io/project/issues?id=twilio_twilio-python&issues=AZJhAlYCyje6SmAfcspM&open=AZJhAlYCyje6SmAfcspM&pullRequest=815">SonarCloud</a></p>

Check failure

Code scanning / SonarCloud

JWT should be signed and verified High

Don't use a JWT token without verifying its signature. See more on SonarCloud

# 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
60 changes: 34 additions & 26 deletions twilio/base/client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.auth_strategy.auth_type import AuthType
from twilio.credential.credential_provider import CredentialProvider


class ClientBase(object):
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -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 """
Expand All @@ -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
Expand All @@ -87,15 +88,21 @@ def request(

:returns: Response from the Twilio API
"""
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
if self.credential_provider:
auth_strategy = self.credential_provider.to_auth_strategy()
headers["Authorization"] = auth_strategy.get_auth_string()
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)
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,
Expand All @@ -118,7 +125,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
Expand All @@ -140,15 +146,22 @@ 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)

##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()
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)
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,
Expand Down Expand Up @@ -246,8 +259,3 @@ def __repr__(self) -> str:
:returns: Machine friendly representation
"""
return "<Twilio {}>".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)
5 changes: 0 additions & 5 deletions twilio/base/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand All @@ -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.
Expand All @@ -94,5 +90,4 @@ async def request_async(
auth=auth,
timeout=timeout,
allow_redirects=allow_redirects,
is_oauth=is_oauth
)
Loading
Loading