diff --git a/license_manager/apps/api/mixins.py b/license_manager/apps/api/mixins.py index ad622d24..1aee9356 100644 --- a/license_manager/apps/api/mixins.py +++ b/license_manager/apps/api/mixins.py @@ -1,6 +1,9 @@ from functools import cached_property +from rest_framework.exceptions import ParseError + from license_manager.apps.api import utils +from license_manager.apps.api_client.lms import LMSApiClient class UserDetailsFromJwtMixin: @@ -18,9 +21,17 @@ def decoded_jwt(self): return utils.get_decoded_jwt(self.request) - @property + @cached_property def lms_user_id(self): - return utils.get_key_from_jwt(self.decoded_jwt, 'user_id') + """ + Retrieve the LMS user ID. + """ + try: + return utils.get_key_from_jwt(self.decoded_jwt, 'user_id') + except ParseError: + lms_client = LMSApiClient() + user_id = lms_client.fetch_lms_user_id(self.request.user.email) + return user_id @property def user_email(self): diff --git a/license_manager/apps/api/v1/tests/test_views.py b/license_manager/apps/api/v1/tests/test_views.py index 9c7efc8a..cd4e3e34 100644 --- a/license_manager/apps/api/v1/tests/test_views.py +++ b/license_manager/apps/api/v1/tests/test_views.py @@ -3227,17 +3227,23 @@ def test_get_subsidy_missing_course_key(self): assert response.status_code == status.HTTP_400_BAD_REQUEST @mock.patch('license_manager.apps.api.v1.views.utils.get_decoded_jwt') - def test_get_subsidy_no_jwt(self, mock_get_decoded_jwt): + @mock.patch('license_manager.apps.api.mixins.LMSApiClient') + def test_get_subsidy_no_jwt(self, MockLMSApiClient, mock_get_decoded_jwt): """ - Verify the view returns a 400 if the user_id could not be found in the JWT. + Verify the view makes an API call to fetch lmsUserId if user_id could not be found in the JWT. """ self._assign_learner_roles() mock_get_decoded_jwt.return_value = {} url = self._get_url_with_params() + + # Mock the behavior of LMSApiClient to return a sample user ID + mock_lms_client = MockLMSApiClient.return_value + mock_lms_client.fetch_lms_user_id.return_value = 443 response = self.api_client.get(url) + assert status.HTTP_404_NOT_FOUND == response.status_code - assert status.HTTP_400_BAD_REQUEST == response.status_code - assert '`user_id` is required and could not be found in your jwt' in str(response.content) + # Assert that the LMSApiClient.fetch_lms_user_id method was called once with the correct argument + mock_lms_client.fetch_lms_user_id.assert_called_once_with(self.user.email) def test_get_subsidy_no_subscription_for_enterprise_customer(self): """ diff --git a/license_manager/apps/api_client/lms.py b/license_manager/apps/api_client/lms.py new file mode 100644 index 00000000..21b8eed3 --- /dev/null +++ b/license_manager/apps/api_client/lms.py @@ -0,0 +1,43 @@ +import logging + +import requests +from django.conf import settings + +from license_manager.apps.api_client.base_oauth import BaseOAuthClient + + +logger = logging.getLogger(__name__) + + +class LMSApiClient(BaseOAuthClient): + """ + API client for calls to the LMS. + """ + api_base_url = settings.LMS_URL + user_details_endpoint = api_base_url + '/api/user/v1/accounts' + + def fetch_lms_user_id(self, email): + """ + Fetch user details for the specified user email. + + Arguments: + email (str): Email of the user for which we want to fetch details for. + + Returns: + str: lms_user_id of the user. + """ + # {base_api_url}/api/user/v1/accounts?email=edx@example.com + try: + query_params = {'email': email} + response = self.client.get(self.user_details_endpoint, params=query_params) + response.raise_for_status() + response_json = response.json() + return response_json[0].get('id') + except requests.exceptions.HTTPError as exc: + logger.error( + 'Failed to fetch user details for user {email} because {reason}'.format( + email=email, + reason=str(exc), + ) + ) + raise exc