diff --git a/README.md b/README.md index 50cc5509e..c3afaf1fa 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_calls.py) + ## Use the helper library ### API Credentials diff --git a/examples/organization_api_calls.py b/examples/organization_api_calls.py new file mode 100644 index 000000000..e94ef9bbe --- /dev/null +++ b/examples/organization_api_calls.py @@ -0,0 +1,28 @@ +import os + +from twilio.rest import Client +from twilio.credential.orgs_credential_provider import OrgsCredentialProvider + +API_KEY = os.environ.get("TWILIO_API_KEY") +API_SECRET = os.environ.get("TWILIO_API_SECRET") +ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID") +CLIENT_ID = os.environ.get("TWILIO_CLIENT_ID") +CLIENT_SECRET = os.environ.get("TWILIO_CLIENT_SECRET") +ORGS_SID = os.environ.get("TWILIO_ORGS_SID") + + +def example(self=None): + """ + Some example usage of fetching accounts within an organization + """ + 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=ORGS_SID).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 000000000..22065738a --- /dev/null +++ b/examples/public_oauth.py @@ -0,0 +1,35 @@ +import os + +from twilio.rest import Client +from twilio.credential.client_credential_provider import ClientCredentialProvider + +API_KEY = os.environ.get("TWILIO_API_KEY") +API_SECRET = os.environ.get("TWILIO_API_SECRET") +ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID") +CLIENT_ID = os.environ.get("TWILIO_CLIENT_ID") +CLIENT_SECRET = os.environ.get("TWILIO_CLIENT_SECRET") +ORGS_SID = os.environ.get("TWILIO_ORGS_SID") +FROM_NUMBER = os.environ.get("TWILIO_FROM_NUMBER") +TO_NUMBER = os.environ.get("TWILIO_TO_NUMBER") + + +def example(self=None): + """ + Some example usage of fetching accounts within an organization + """ + 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 000000000..d756fd088 --- /dev/null +++ b/twilio/credential/client_credential_provider.py @@ -0,0 +1,28 @@ +from twilio.http.orgs_token_manager import OrgTokenManager +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 = OrgTokenManager( + 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 000000000..d65acb2ba --- /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