"""Helper functions for working with Credentials.""" import logging from urllib.parse import urljoin import requests from django.conf import settings from django.contrib.auth import get_user_model from edx_rest_api_client.auth import SuppliedJwtAuth from openedx.core.djangoapps.credentials.models import CredentialsApiConfig from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user from openedx.core.lib.edx_api_utils import get_api_data log = logging.getLogger(__name__) User = get_user_model() def get_credentials_records_url(program_uuid=None): """ Returns a URL for a given records page (or general records list if given no UUID). May return None if this feature is disabled. Arguments: program_uuid (str): Optional program uuid to link for a program records URL """ base_url = CredentialsApiConfig.current().public_records_url if base_url is None: return None if program_uuid: # Credentials expects Program UUIDs without dashes so we remove them here stripped_program_uuid = program_uuid.replace("-", "") return urljoin(base_url, f"programs/{stripped_program_uuid}") return base_url def get_credentials_api_client(user): """ Returns an authenticated Credentials API client. Arguments: user (User): The user to authenticate as when requesting credentials. """ scopes = ['email', 'profile', 'user_id'] jwt = create_jwt_for_user(user, scopes=scopes) client = requests.Session() client.auth = SuppliedJwtAuth(jwt) return client def get_credentials_api_base_url(org=None): """ Returns a credentials API base URL. Arguments: org (str): Optional organization to look up the site config for, rather than the current request """ if org is None: url = CredentialsApiConfig.current().internal_api_url # by current request else: url = CredentialsApiConfig.get_internal_api_url_for_org(org) # by org return url def get_credentials(user, program_uuid=None, credential_type=None): """ Given a user, get credentials earned from the credentials service. Arguments: user (User): The user to authenticate as when requesting credentials. Keyword Arguments: program_uuid (str): UUID of the program whose credential to retrieve. credential_type (str): Which type of credentials to return (course-run or program) Returns: list of dict, representing credentials returned by the Credentials service. """ credential_configuration = CredentialsApiConfig.current() querystring = {'username': user.username, 'status': 'awarded', 'only_visible': 'True'} if program_uuid: querystring['program_uuid'] = program_uuid if credential_type: querystring['type'] = credential_type # Bypass caching for staff users, who may be generating credentials and # want to see them displayed immediately. use_cache = credential_configuration.is_cache_enabled and not user.is_staff cache_key = f'{credential_configuration.CACHE_KEY}.{user.username}' if use_cache else None if cache_key and program_uuid: cache_key = f'{cache_key}.{program_uuid}' api_client = get_credentials_api_client(user) base_api_url = get_credentials_api_base_url() return get_api_data( credential_configuration, 'credentials', api_client=api_client, base_api_url=base_api_url, querystring=querystring, cache_key=cache_key ) def get_courses_completion_status(username, course_run_ids): """ Given the username and course run ids, checks for course completion status Arguments: username (User): Username of the user whose credentials are being requested. course_run_ids(List): list of course run ids for which we need to check the completion status Returns: list of course_run_ids for which user has completed the course Boolean: True if an exception occurred while calling the api, False otherwise """ credential_configuration = CredentialsApiConfig.current() if not credential_configuration.enabled: log.warning('%s configuration is disabled.', credential_configuration.API_NAME) return [], False completion_status_url = (f'{settings.CREDENTIALS_INTERNAL_SERVICE_URL}/api' '/credentials/v1/learner_cert_status/') try: api_client = get_credentials_api_client( User.objects.get(username=settings.CREDENTIALS_SERVICE_USERNAME) ) api_response = api_client.post( completion_status_url, json={ 'username': username, 'course_runs': course_run_ids, } ) api_response.raise_for_status() course_completion_response = api_response.json() except Exception as exc: # pylint: disable=broad-except log.exception("An unexpected error occurred while reqeusting course completion statuses " "for user [%s] for course_run_ids [%s] with exc [%s]:", username, course_run_ids, exc ) return [], True log.info("Course completion status response for user [%s] for course_run_ids [%s] is [%s]", username, course_run_ids, course_completion_response) # Yes, This is course_credentials_data. The key is named status but # it contains all the courses data from credentials. course_credentials_data = course_completion_response.get('status', []) if course_credentials_data is not None: filtered_records = [course_data['course_run']['key'] for course_data in course_credentials_data if course_data['course_run']['key'] in course_run_ids and course_data['status'] == settings.CREDENTIALS_COURSE_COMPLETION_STATE] return filtered_records, False return [], False