def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ course_discovery_client = Client.objects.get(name=CLIENT_NAME) return EdxRestApiClient( course_discovery_client.url, jwt=get_id_token(user, CLIENT_NAME) )
def get(self, request, *args, **kwargs): """Generate and return a token, if the integration is enabled.""" if ProgramsApiConfig.current().is_studio_tab_enabled: id_token = get_id_token(request.user, 'programs') return JsonResponse({'id_token': id_token}) else: raise Http404
def test_get_id_token(self): """Verify that ID tokens are signed with the correct secret and generated with the correct claims.""" token = get_id_token(self.user, self.client_name) payload = jwt.decode( token, self.oauth2_client.client_secret, audience=self.oauth2_client.client_id, issuer=settings.OAUTH_OIDC_ISSUER, ) now = datetime.datetime.utcnow() expiration = now + datetime.timedelta( seconds=settings.OAUTH_ID_TOKEN_EXPIRATION) expected_payload = { 'preferred_username': self.user.username, 'name': self.user_profile.name, 'email': self.user.email, 'administrator': self.user.is_staff, 'iss': settings.OAUTH_OIDC_ISSUER, 'exp': calendar.timegm(expiration.utctimetuple()), 'iat': calendar.timegm(now.utctimetuple()), 'aud': self.oauth2_client.client_id, 'sub': self.user.id, # pylint: disable=no-member } self.assertEqual(payload, expected_payload)
def test_get_id_token(self): """Verify that ID tokens are signed with the correct secret and generated with the correct claims.""" token = get_id_token(self.user, self.client_name) payload = jwt.decode( token, self.oauth2_client.client_secret, audience=self.oauth2_client.client_id, issuer=settings.OAUTH_OIDC_ISSUER, ) now = datetime.datetime.utcnow() expiration = now + datetime.timedelta(seconds=settings.OAUTH_ID_TOKEN_EXPIRATION) expected_payload = { 'preferred_username': self.user.username, 'name': self.user_profile.name, 'email': self.user.email, 'administrator': self.user.is_staff, 'iss': settings.OAUTH_OIDC_ISSUER, 'exp': calendar.timegm(expiration.utctimetuple()), 'iat': calendar.timegm(now.utctimetuple()), 'aud': self.oauth2_client.client_id, 'sub': anonymous_id_for_user(self.user, None), } self.assertEqual(payload, expected_payload)
def get_edx_api_data(api_config, user, resource, api=None, resource_id=None, querystring=None, cache_key=None): """GET data from an edX REST API. DRY utility for handling caching and pagination. Arguments: api_config (ConfigurationModel): The configuration model governing interaction with the API. user (User): The user to authenticate as when requesting data. resource (str): Name of the API resource being requested. Keyword Arguments: api (APIClient): API client to use for requesting data. resource_id (int or str): Identifies a specific resource to be retrieved. querystring (dict): Optional query string parameters. cache_key (str): Where to cache retrieved data. The cache will be ignored if this is omitted (neither inspected nor updated). Returns: Data returned by the API. When hitting a list endpoint, extracts "results" (list of dict) returned by DRF-powered APIs. """ no_data = [] if not api_config.enabled: log.warning('%s configuration is disabled.', api_config.API_NAME) return no_data if cache_key: cache_key = '{}.{}'.format(cache_key, resource_id) if resource_id else cache_key cached = cache.get(cache_key) if cached: return cached try: if not api: jwt = get_id_token(user, api_config.OAUTH2_CLIENT_NAME) api = EdxRestApiClient(api_config.internal_api_url, jwt=jwt) except: # pylint: disable=bare-except log.exception('Failed to initialize the %s API client.', api_config.API_NAME) return no_data try: endpoint = getattr(api, resource) querystring = querystring if querystring else {} response = endpoint(resource_id).get(**querystring) if resource_id: results = response else: results = _traverse_pagination(response, endpoint, querystring, no_data) except: # pylint: disable=bare-except log.exception('Failed to retrieve data from the %s API.', api_config.API_NAME) return no_data if cache_key: cache.set(cache_key, results, api_config.cache_ttl) return results
def test_get_id_token(self, has_profile): """Verify that ID tokens are signed with the correct secret and generated with the correct claims.""" full_name = UserProfileFactory( user=self.user).name if has_profile else None token = get_id_token(self.user, self.client_name) payload = jwt.decode( token, self.oauth2_client.client_secret, audience=self.oauth2_client.client_id, issuer=settings.OAUTH_OIDC_ISSUER, ) now = datetime.datetime.utcnow() expiration = now + datetime.timedelta( seconds=settings.OAUTH_ID_TOKEN_EXPIRATION) expected_payload = { 'preferred_username': self.user.username, 'name': full_name, 'email': self.user.email, 'administrator': self.user.is_staff, 'iss': settings.OAUTH_OIDC_ISSUER, 'exp': calendar.timegm(expiration.utctimetuple()), 'iat': calendar.timegm(now.utctimetuple()), 'aud': self.oauth2_client.client_id, 'sub': anonymous_id_for_user(self.user, None), } self.assertEqual(payload, expected_payload)
def get_edx_api_data(api_config, user, resource, api=None, resource_id=None, querystring=None, cache_key=None): """GET data from an edX REST API. DRY utility for handling caching and pagination. Arguments: api_config (ConfigurationModel): The configuration model governing interaction with the API. user (User): The user to authenticate as when requesting data. resource (str): Name of the API resource being requested. Keyword Arguments: api (APIClient): API client to use for requesting data. resource_id (int or str): Identifies a specific resource to be retrieved. querystring (dict): Optional query string parameters. cache_key (str): Where to cache retrieved data. The cache will be ignored if this is omitted (neither inspected nor updated). Returns: Data returned by the API. When hitting a list endpoint, extracts "results" (list of dict) returned by DRF-powered APIs. """ no_data = [] if not api_config.enabled: log.warning("%s configuration is disabled.", api_config.API_NAME) return no_data if cache_key: cache_key = "{}.{}".format(cache_key, resource_id) if resource_id else cache_key cached = cache.get(cache_key) if cached: return cached try: if not api: jwt = get_id_token(user, api_config.OAUTH2_CLIENT_NAME) api = EdxRestApiClient(api_config.internal_api_url, jwt=jwt) except: # pylint: disable=bare-except log.exception("Failed to initialize the %s API client.", api_config.API_NAME) return no_data try: endpoint = getattr(api, resource) querystring = querystring if querystring else {} response = endpoint(resource_id).get(**querystring) if resource_id: results = response else: results = _traverse_pagination(response, endpoint, querystring, no_data) except: # pylint: disable=bare-except log.exception("Failed to retrieve data from the %s API.", api_config.API_NAME) return no_data if cache_key: cache.set(cache_key, results, api_config.cache_ttl) return results
def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ course_discovery_client = Client.objects.get(name=CLIENT_NAME) secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return EdxRestApiClient( course_discovery_client.url, jwt=get_id_token(user, CLIENT_NAME, secret_key=secret_key) )
def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ course_discovery_client = Client.objects.get(name=CLIENT_NAME) secret_key = helpers.get_value('JWT_AUTH', settings.JWT_AUTH)['JWT_SECRET_KEY'] return EdxRestApiClient(course_discovery_client.url, jwt=get_id_token(user, CLIENT_NAME, secret_key=secret_key))
def get_edx_api_data(api_config, user, resource, querystring=None, cache_key=None): """Fetch data from an API using provided API configuration and resource name. Arguments: api_config (ConfigurationModel): The configuration model governing interaction with the API. user (User): The user to authenticate as when requesting data. resource(str): Name of the API resource for which data is being requested. querystring(dict): Querystring parameters that might be required to request data. cache_key(str): Where to cache retrieved data. Omitting this will cause the cache to be bypassed. Returns: list of dict, representing data returned by the API. """ no_data = [] if not api_config.enabled: log.warning('%s configuration is disabled.', api_config.API_NAME) return no_data if cache_key: cached = cache.get(cache_key) if cached is not None: return cached try: jwt = get_id_token(user, api_config.OAUTH2_CLIENT_NAME) api = EdxRestApiClient(api_config.internal_api_url, jwt=jwt) except Exception: # pylint: disable=broad-except log.exception('Failed to initialize the %s API client.', api_config.API_NAME) return no_data try: querystring = {} if not querystring else querystring response = getattr(api, resource).get(**querystring) results = response.get('results', no_data) page = 1 next_page = response.get('next', None) while next_page: page += 1 querystring['page'] = page response = getattr(api, resource).get(**querystring) results += response.get('results', no_data) next_page = response.get('next', None) except Exception: # pylint: disable=broad-except log.exception('Failed to retrieve data from the %s API.', api_config.API_NAME) return no_data if cache_key: cache.set(cache_key, results, api_config.cache_ttl) return results
def get_api_client(api_config, student): """ Create and configure an API client for authenticated HTTP requests. Args: api_config: ProgramsApiConfig or CredentialsApiConfig object student: User object as whom to authenticate to the API Returns: EdxRestApiClient """ id_token = get_id_token(student, api_config.OAUTH2_CLIENT_NAME) return EdxRestApiClient(api_config.internal_api_url, jwt=id_token)
def get_programs(user): """Given a user, get programs from the Programs service. Returned value is cached depending on user permissions. Staff users making requests against Programs will receive unpublished programs, while regular users will only receive published programs. Arguments: user (User): The user to authenticate as when requesting programs. Returns: list of dict, representing programs returned by the Programs service. """ programs_config = ProgramsApiConfig.current() no_programs = [] # Bypass caching for staff users, who may be creating Programs and want to see them displayed immediately. use_cache = programs_config.is_cache_enabled and not user.is_staff if not programs_config.enabled: log.warning('Programs configuration is disabled.') return no_programs if use_cache: cached = cache.get(programs_config.CACHE_KEY) if cached is not None: return cached try: jwt = get_id_token(user, programs_config.OAUTH2_CLIENT_NAME) api = EdxRestApiClient(programs_config.internal_api_url, jwt=jwt) except Exception: # pylint: disable=broad-except log.exception('Failed to initialize the Programs API client.') return no_programs try: response = api.programs.get() except Exception: # pylint: disable=broad-except log.exception('Failed to retrieve programs from the Programs API.') return no_programs results = response.get('results', no_programs) if use_cache: cache.set(programs_config.CACHE_KEY, results, programs_config.cache_ttl) return results
def get_edxnotes_id_token(user): """ Returns generated ID Token for edxnotes. """ return get_id_token(user, CLIENT_NAME)
def test_get_id_token_invalid_client(self): """Verify that ImproperlyConfigured is raised when an invalid client name is provided.""" with self.assertRaises(ImproperlyConfigured): get_id_token(self.user, 'does-not-exist')