From 88a8887bab05f3468386292d523e1f03edb14663 Mon Sep 17 00:00:00 2001 From: Johan Castiblanco Date: Fri, 5 Jul 2024 18:57:51 -0500 Subject: [PATCH 1/2] refactor: sms vendor with notification-gateway api --- eox_nelp/api_clients/authenticators.py | 30 +++++++++++++++++++ eox_nelp/api_clients/sms_vendor.py | 15 ++++++---- eox_nelp/api_clients/tests/test_sms_vendor.py | 22 +++++++------- eox_nelp/settings/test.py | 2 +- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/eox_nelp/api_clients/authenticators.py b/eox_nelp/api_clients/authenticators.py index 2f2f9932..534fbcb1 100644 --- a/eox_nelp/api_clients/authenticators.py +++ b/eox_nelp/api_clients/authenticators.py @@ -127,3 +127,33 @@ def authenticate(self, api_client): ) return session + + +class SMSVendoAuthenticator(BasicAuthAuthenticator): + """Authenticator for custom use of the SMS vendor particular API.""" + + def authenticate(self, api_client): + """Authenticate the session with basic auth in order to get Bearer token. + Then the Bearer token is added to a new session Headers. + SMS vendor configuration. + """ + auth_session = super().authenticate(api_client) + key = f"smsvendor-{api_client.user}-{api_client.password}" + headers = cache.get(key) + + if not headers: + authenticate_url = f"{api_client.base_url}/oauth2/v1/token" + response = auth_session.post( + url=authenticate_url, + data={"grant_type": "client_credentials", "scope": "notification"} + ).json() + headers = { + "Authorization": f"{response.get('token_type')} {response.get('access_token')}" + } + + cache.set(key, headers, int(response.get("expires_in", 300))) + + session = requests.Session() + session.headers.update(headers) + + return session diff --git a/eox_nelp/api_clients/sms_vendor.py b/eox_nelp/api_clients/sms_vendor.py index 14494db1..6e1aa33f 100644 --- a/eox_nelp/api_clients/sms_vendor.py +++ b/eox_nelp/api_clients/sms_vendor.py @@ -6,6 +6,7 @@ from django.conf import settings from eox_nelp.api_clients import AbstractAPIRestClient +from eox_nelp.api_clients.authenticators import SMSVendoAuthenticator try: from eox_audit_model.decorators import audit_method @@ -17,11 +18,18 @@ def audit_method(action): # pylint: disable=unused-argument class SMSVendorApiClient(AbstractAPIRestClient): """Allow to perform SMS send operations.""" + authentication_class = SMSVendoAuthenticator @property def base_url(self): return getattr(settings, "SMS_VENDOR_URL") + def __init__(self): + self.user = getattr(settings, "SMS_VENDOR_USERNAME") + self.password = getattr(settings, "SMS_VENDOR_PASSWORD") + + super().__init__() + def send_sms(self, recipient, message): """This send SMS using an external Vendor via API. @@ -37,11 +45,8 @@ def send_sms_request(recipient, message): """This is a wrapper that allows to make audit-able the send_SMS method.""" path = getattr(settings, "SMS_VENDOR_SEND_SMS_PATH", "") payload = { - "message": message, - "number": recipient, - "username": getattr(settings, "SMS_VENDOR_USERNAME"), - "password": getattr(settings, "SMS_VENDOR_PASSWORD"), - "sender": getattr(settings, "SMS_VENDOR_MSG_SENDER", "NELC"), + "sms_message": message, + "recipient_number": recipient, } return self.make_post(path, payload) diff --git a/eox_nelp/api_clients/tests/test_sms_vendor.py b/eox_nelp/api_clients/tests/test_sms_vendor.py index 630e9736..ddd97c1f 100644 --- a/eox_nelp/api_clients/tests/test_sms_vendor.py +++ b/eox_nelp/api_clients/tests/test_sms_vendor.py @@ -5,8 +5,7 @@ """ import unittest -from django.conf import settings -from mock import patch +from mock import Mock, patch from eox_nelp.api_clients.sms_vendor import SMSVendorApiClient from eox_nelp.api_clients.tests.mixins import TestRestApiClientMixin @@ -20,6 +19,7 @@ def setUp(self): self.api_class = SMSVendorApiClient @patch.object(SMSVendorApiClient, "make_post") + @patch.object(SMSVendorApiClient, "_authenticate", Mock()) def test_send_sms(self, post_mock): """Test successful post request. @@ -27,21 +27,21 @@ def test_send_sms(self, post_mock): - Response is the expected value """ expected_value = { - "message": "SMS sent =), waiting what would be this field.", - "responseCode": 200, + "message": "Operation completed successfully", + "transaction_id": "50693df-665d-47e1-affb-01076a83b9023427", + "recipient": "+573219990000", + "timestamp": "1720220972275" } post_mock.return_value = expected_value - recipient = 3219802890 + recipient = "+573219990000" message = "This is a message to test SMS integration." api_client = self.api_class() expected_payload = { - "message": message, - "number": recipient, - "username": settings.SMS_VENDOR_USERNAME, - "password": settings.SMS_VENDOR_PASSWORD, - "sender": settings.SMS_VENDOR_MSG_SENDER, + "sms_message": message, + "recipient_number": recipient, } + response = api_client.send_sms(recipient, message) self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("", expected_payload) + post_mock.assert_called_once_with("sms/send", expected_payload) diff --git a/eox_nelp/settings/test.py b/eox_nelp/settings/test.py index 15ae03c1..62aafdd3 100644 --- a/eox_nelp/settings/test.py +++ b/eox_nelp/settings/test.py @@ -53,7 +53,7 @@ def plugin_settings(settings): # pylint: disable=function-redefined settings.SMS_VENDOR_URL = 'https://testing.com' settings.SMS_VENDOR_USERNAME = 'test-user' settings.SMS_VENDOR_PASSWORD = 'test-password' - settings.SMS_VENDOR_MSG_SENDER = 'Nelc-test' + settings.SMS_VENDOR_SEND_SMS_PATH = 'sms/send' settings.PEARSON_RTI_WSDL_URL = 'https://testing.com' settings.PEARSON_RTI_CERT = '/openedx/certs/cert.p12' From 15dc0d5980a733bf072eb03d04e2908f5646db56 Mon Sep 17 00:00:00 2001 From: Johan Castiblanco Date: Mon, 15 Jul 2024 17:15:07 -0500 Subject: [PATCH 2/2] refactor: extende OAUTH2Basic authenticator --- eox_nelp/api_clients/authenticators.py | 18 +++++++++++------- eox_nelp/api_clients/sms_vendor.py | 5 +++-- eox_nelp/settings/test.py | 1 + 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/eox_nelp/api_clients/authenticators.py b/eox_nelp/api_clients/authenticators.py index 534fbcb1..00ecad77 100644 --- a/eox_nelp/api_clients/authenticators.py +++ b/eox_nelp/api_clients/authenticators.py @@ -129,20 +129,24 @@ def authenticate(self, api_client): return session -class SMSVendoAuthenticator(BasicAuthAuthenticator): - """Authenticator for custom use of the SMS vendor particular API.""" +class Oauth2BasicAuthenticator(BasicAuthAuthenticator): + """Authenticator for custom use using basic auth to get a Oauth2 Token (Bearer or JWT). + Token_type on depends of the response used after the oauth2 token request. + Then the token is used for the next requests. + """ def authenticate(self, api_client): - """Authenticate the session with basic auth in order to get Bearer token. - Then the Bearer token is added to a new session Headers. - SMS vendor configuration. + """Authenticate the session with basic auth in order to get token(Bearer or JWT). + Then the token is added to a new session Headers. + Is needed the user, password and token_path class atrributes to the get oauth2 token, + based on the client configuration. """ auth_session = super().authenticate(api_client) - key = f"smsvendor-{api_client.user}-{api_client.password}" + key = f"oauth2-basic-{api_client.user}-{api_client.password}" headers = cache.get(key) if not headers: - authenticate_url = f"{api_client.base_url}/oauth2/v1/token" + authenticate_url = f"{api_client.base_url}/{api_client.token_path}" response = auth_session.post( url=authenticate_url, data={"grant_type": "client_credentials", "scope": "notification"} diff --git a/eox_nelp/api_clients/sms_vendor.py b/eox_nelp/api_clients/sms_vendor.py index 6e1aa33f..9edf3e55 100644 --- a/eox_nelp/api_clients/sms_vendor.py +++ b/eox_nelp/api_clients/sms_vendor.py @@ -6,7 +6,7 @@ from django.conf import settings from eox_nelp.api_clients import AbstractAPIRestClient -from eox_nelp.api_clients.authenticators import SMSVendoAuthenticator +from eox_nelp.api_clients.authenticators import Oauth2BasicAuthenticator try: from eox_audit_model.decorators import audit_method @@ -18,7 +18,7 @@ def audit_method(action): # pylint: disable=unused-argument class SMSVendorApiClient(AbstractAPIRestClient): """Allow to perform SMS send operations.""" - authentication_class = SMSVendoAuthenticator + authentication_class = Oauth2BasicAuthenticator @property def base_url(self): @@ -27,6 +27,7 @@ def base_url(self): def __init__(self): self.user = getattr(settings, "SMS_VENDOR_USERNAME") self.password = getattr(settings, "SMS_VENDOR_PASSWORD") + self.token_path = getattr(settings, "SMS_VENDOR_TOKEN_PATH") super().__init__() diff --git a/eox_nelp/settings/test.py b/eox_nelp/settings/test.py index 62aafdd3..542c0f3a 100644 --- a/eox_nelp/settings/test.py +++ b/eox_nelp/settings/test.py @@ -53,6 +53,7 @@ def plugin_settings(settings): # pylint: disable=function-redefined settings.SMS_VENDOR_URL = 'https://testing.com' settings.SMS_VENDOR_USERNAME = 'test-user' settings.SMS_VENDOR_PASSWORD = 'test-password' + settings.SMS_VENDOR_TOKEN_PATH = "oauth2/v1/token" settings.SMS_VENDOR_SEND_SMS_PATH = 'sms/send' settings.PEARSON_RTI_WSDL_URL = 'https://testing.com'