From 677da6cc094e1ba09a1a3266221ad4e4e97a44d1 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 12 Dec 2024 15:32:57 +0530 Subject: [PATCH 1/4] Docs update and examples --- README.md | 7 ++++ examples/organization_api.py | 31 ++++++++++++++ examples/public_oauth.py | 37 +++++++++++++++++ .../credential/client_credential_provider.py | 28 +++++++++++++ twilio/http/client_token_manager.py | 41 +++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 examples/organization_api.py create mode 100644 examples/public_oauth.py create mode 100644 twilio/credential/client_credential_provider.py create mode 100644 twilio/http/client_token_manager.py diff --git a/README.md b/README.md index 50cc5509e1..bfbd6a14b5 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,13 @@ After a brief delay, you will receive the text message on your phone. > **Warning** > It's okay to hardcode your credentials when testing locally, but you should use environment variables to keep them secret before committing any code or deploying to production. Check out [How to Set Environment Variables](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html) for more information. +## OAuth Feature for Twilio APIs +We are introducing Client Credentials Flow-based OAuth 2.0 authentication. This feature is currently in beta and its implementation is subject to change. + +API examples [here](https://github.com/twilio/twilio-python/blob/main/examples/public_oauth.py) + +Organisation API examples [here](https://github.com/twilio/twilio-python/blob/main/examples/organization_api.py) + ## Use the helper library ### API Credentials diff --git a/examples/organization_api.py b/examples/organization_api.py new file mode 100644 index 0000000000..3619c2a100 --- /dev/null +++ b/examples/organization_api.py @@ -0,0 +1,31 @@ +import os + +from twilio.rest import Client +from twilio.credential.orgs_credential_provider import OrgsCredentialProvider + +ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID") +API_KEY = os.environ.get("TWILIO_API_KEY") +API_SECRET = os.environ.get("TWILIO_API_SECRET") + +CLIENT_ID = os.environ.get("TWILIO_CLIENT_ID") +CLIENT_SECRET = os.environ.get("CLIENT_SECRET") +ORGS_SID = os.environ.get("ORGS_SID") + + +def example(): + """ + Some example usage of using organization resources + """ + self.client = Client( + username=API_KEY, + password=API_SECRET, + account_sid=ACCOUNT_SID, + credential_provider= OrgsCredentialProvider(CLIENT_ID,CLIENT_SECRET) + ) + + accounts = self.client.preview_iam.organization(organization_sid="OR64adedc0f4dc99b9113305f725677b47").accounts.stream() + self.assertIsNotNone(accounts) + + +if __name__ == "__main__": + example() diff --git a/examples/public_oauth.py b/examples/public_oauth.py new file mode 100644 index 0000000000..10487d08a7 --- /dev/null +++ b/examples/public_oauth.py @@ -0,0 +1,37 @@ +import os + +from twilio.rest import Client +from twilio.credential.client_credential_provider import ClientCredentialProvider + +ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID") +API_KEY = os.environ.get("TWILIO_API_KEY") +API_SECRET = os.environ.get("TWILIO_API_SECRET") +FROM_NUMBER = os.environ.get("TWILIO_FROM_NUMBER") +TO_NUMBER = os.environ.get("TWILIO_TO_NUMBER") + +CLIENT_ID = os.environ.get("TWILIO_CLIENT_ID") +CLIENT_SECRET = os.environ.get("CLIENT_SECRET") + + +def example(): + """ + Some example usage of message resources. + """ + self.client = Client( + username=API_KEY, + password=API_SECRET, + account_sid=ACCOUNT_SID, + credential_provider= ClientCredentialProvider(CLIENT_ID,CLIENT_SECRET) + ) + + msg = self.client.messages.create( + to=self.to_number, from_=self.from_number, body="hello world" + ) + self.assertEqual(msg.to, self.to_number) + self.assertEqual(msg.from_, self.from_number) + self.assertEqual(msg.body, "hello world") + self.assertIsNotNone(msg.sid) + + +if __name__ == "__main__": + example() diff --git a/twilio/credential/client_credential_provider.py b/twilio/credential/client_credential_provider.py new file mode 100644 index 0000000000..656d446395 --- /dev/null +++ b/twilio/credential/client_credential_provider.py @@ -0,0 +1,28 @@ +from twilio.http.client_token_manager import ClientTokenManager +from twilio.base.exceptions import TwilioException +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 ClientCredentialProvider(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("Client id and Client secret are mandatory") + + self.grant_type = "client_credentials" + 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 = ClientTokenManager( + self.grant_type, self.client_id, self.client_secret + ) + if self.auth_strategy is None: + self.auth_strategy = TokenAuthStrategy(self.token_manager) + return self.auth_strategy diff --git a/twilio/http/client_token_manager.py b/twilio/http/client_token_manager.py new file mode 100644 index 0000000000..d65acb2ba2 --- /dev/null +++ b/twilio/http/client_token_manager.py @@ -0,0 +1,41 @@ +from twilio.http.token_manager import TokenManager +from twilio.rest import Client + + +class ClientTokenManager(TokenManager): + """ + Client 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 + self.client = Client() + + def fetch_access_token(self): + token_instance = self.client.preview_iam.v1.token.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 From 89377b73ed7427de0b2452987f43e9d86d372e90 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 12 Dec 2024 15:44:56 +0530 Subject: [PATCH 2/4] Docs update and examples --- examples/organization_api.py | 2 -- examples/public_oauth.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/examples/organization_api.py b/examples/organization_api.py index 3619c2a100..feab6a24a3 100644 --- a/examples/organization_api.py +++ b/examples/organization_api.py @@ -17,8 +17,6 @@ def example(): Some example usage of using organization resources """ self.client = Client( - username=API_KEY, - password=API_SECRET, account_sid=ACCOUNT_SID, credential_provider= OrgsCredentialProvider(CLIENT_ID,CLIENT_SECRET) ) diff --git a/examples/public_oauth.py b/examples/public_oauth.py index 10487d08a7..41d6e4b646 100644 --- a/examples/public_oauth.py +++ b/examples/public_oauth.py @@ -18,8 +18,6 @@ def example(): Some example usage of message resources. """ self.client = Client( - username=API_KEY, - password=API_SECRET, account_sid=ACCOUNT_SID, credential_provider= ClientCredentialProvider(CLIENT_ID,CLIENT_SECRET) ) From 80c157abf409e132bf61028a88861e53f704a552 Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 12 Dec 2024 15:51:43 +0530 Subject: [PATCH 3/4] Docs update and examples --- examples/organization_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/organization_api.py b/examples/organization_api.py index feab6a24a3..72ad12e4ab 100644 --- a/examples/organization_api.py +++ b/examples/organization_api.py @@ -21,7 +21,7 @@ def example(): credential_provider= OrgsCredentialProvider(CLIENT_ID,CLIENT_SECRET) ) - accounts = self.client.preview_iam.organization(organization_sid="OR64adedc0f4dc99b9113305f725677b47").accounts.stream() + accounts = self.client.preview_iam.organization(organization_sid=ORGS_SID).accounts.stream() self.assertIsNotNone(accounts) From 83c24fad77cc7bef26e08ea07d2ffd2430e82b8b Mon Sep 17 00:00:00 2001 From: AsabuHere Date: Thu, 12 Dec 2024 15:55:35 +0530 Subject: [PATCH 4/4] Docs update and examples --- examples/organization_api.py | 3 ++- examples/public_oauth.py | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/organization_api.py b/examples/organization_api.py index 72ad12e4ab..f95df03c72 100644 --- a/examples/organization_api.py +++ b/examples/organization_api.py @@ -22,7 +22,8 @@ def example(): ) accounts = self.client.preview_iam.organization(organization_sid=ORGS_SID).accounts.stream() - self.assertIsNotNone(accounts) + for record in accounts: + print(record) if __name__ == "__main__": diff --git a/examples/public_oauth.py b/examples/public_oauth.py index 41d6e4b646..a01d04a6c1 100644 --- a/examples/public_oauth.py +++ b/examples/public_oauth.py @@ -25,10 +25,6 @@ def example(): msg = self.client.messages.create( to=self.to_number, from_=self.from_number, body="hello world" ) - self.assertEqual(msg.to, self.to_number) - self.assertEqual(msg.from_, self.from_number) - self.assertEqual(msg.body, "hello world") - self.assertIsNotNone(msg.sid) if __name__ == "__main__":