def _get_jwt_builder(self, user, is_client_restricted): """ Creates and returns a JWTBuilder object for creating JWTs. """ # If JWT scope enforcement is enabled, we need to sign tokens # given to restricted applications with a key that # other IDAs do not have access to. This prevents restricted # applications from getting access to API endpoints available # on other IDAs which have not yet been protected with the # scope-related DRF permission classes. Once all endpoints have # been protected, we can enable all IDAs to use the same new # (asymmetric) key. # TODO: ARCH-162 use_asymmetric_key = ENFORCE_JWT_SCOPES.is_enabled( ) and is_client_restricted monitoring_utils.set_custom_metric('oauth_asymmetric_jwt', use_asymmetric_key) log.info("Using Asymmetric JWT: %s", use_asymmetric_key) return JwtBuilder( user, asymmetric=use_asymmetric_key, secret=settings.JWT_AUTH['JWT_SECRET_KEY'], issuer=settings.JWT_AUTH['JWT_ISSUER'], )
def dispatch(self, request, *args, **kwargs): response = super(AccessTokenView, self).dispatch(request, *args, **kwargs) if response.status_code == 200 and request.POST.get('token_type', '').lower() == 'jwt': client_id = self._get_client_id(request) adapter = self.get_adapter(request) expires_in, scopes, user = self._decompose_access_token_response(adapter, response) issuer, secret, audience, filters, is_client_restricted = self._get_client_specific_claims( client_id, adapter ) content = { 'access_token': JwtBuilder( user, secret=secret, issuer=issuer, ).build_token( scopes, expires_in, aud=audience, additional_claims={ 'filters': filters, 'is_restricted': is_client_restricted, }, ), 'expires_in': expires_in, 'token_type': 'JWT', 'scope': ' '.join(scopes), } response.content = json.dumps(content) return response
def get_course_run(course_key, user): """Get a course run's data from the course catalog service. Arguments: course_key (CourseKey): Course key object identifying the run whose data we want. user (User): The user to authenticate as when making requests to the catalog service. Returns: dict, empty if no data could be retrieved. """ catalog_integration = CatalogIntegration.current() if catalog_integration.enabled: scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) api = EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt) data = get_edx_api_data( catalog_integration, user, 'course_runs', resource_id=unicode(course_key), cache_key=catalog_integration.CACHE_KEY if catalog_integration.is_cache_enabled else None, api=api, ) return data if data else {} else: return {}
def build_jwt_headers(self, user): """ Helper function for creating headers for the JWT authentication. """ token = JwtBuilder(user).build_token([]) headers = {'HTTP_AUTHORIZATION': 'JWT ' + token} return headers
def test_tracking_context(self): """ Ensure the tracking context is set up in the api client correctly and automatically. """ # fake an E-Commerce API request. httpretty.register_uri( httpretty.POST, '{}/baskets/1/'.format(settings.ECOMMERCE_API_URL.strip('/')), status=200, body='{}', adding_headers={'Content-Type': JSON} ) mock_tracker = mock.Mock() mock_tracker.resolve_context = mock.Mock(return_value={'client_id': self.TEST_CLIENT_ID, 'ip': '127.0.0.1'}) with mock.patch('openedx.core.djangoapps.commerce.utils.tracker.get_tracker', return_value=mock_tracker): ecommerce_api_client(self.user).baskets(1).post() # Verify the JWT includes the tracking context for the user actual_header = httpretty.last_request().headers['Authorization'] claims = { 'tracking_context': { 'lms_user_id': self.user.id, # pylint: disable=no-member 'lms_client_id': self.TEST_CLIENT_ID, 'lms_ip': '127.0.0.1', } } expected_jwt = JwtBuilder(self.user).build_token(['email', 'profile'], additional_claims=claims) expected_header = 'JWT {}'.format(expected_jwt) self.assertEqual(actual_header, expected_header)
def __init__(self): """ Initialize an authenticated Discovery service API client by using the provided user. """ catalog_integration = CatalogIntegration.current() # Client can't be used if there is no catalog integration if not (catalog_integration and catalog_integration.enabled): LOGGER.error( "Unable to create DiscoveryApiClient because catalog integration not set up or enabled" ) return None try: user = catalog_integration.get_service_user() except ObjectDoesNotExist: LOGGER.error("Unable to retrieve catalog integration service user") return None jwt = JwtBuilder(user).build_token([]) base_url = configuration_helpers.get_value( 'COURSE_CATALOG_URL_BASE', settings.COURSE_CATALOG_URL_BASE) self.client = EdxRestApiClient('{base_url}{journals_path}'.format( base_url=base_url, journals_path=JOURNALS_API_PATH), jwt=jwt)
def get_credentials_api_client(user): """ Returns an authenticated Credentials API client. """ scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) return EdxRestApiClient(CredentialsApiConfig.current().internal_api_url, jwt=jwt)
def get_api_client(api_config, student): """ Create and configure an API client for authenticated HTTP requests. Args: api_config: CredentialsApiConfig object student: User object as whom to authenticate to the API Returns: EdxRestApiClient """ # TODO: Use the system's JWT_AUDIENCE and JWT_SECRET_KEY instead of client ID and name. client_name = api_config.OAUTH2_CLIENT_NAME try: client = Client.objects.get(name=client_name) except Client.DoesNotExist: raise ImproperlyConfigured( 'OAuth2 Client with name [{}] does not exist.'.format(client_name)) scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(student, secret=client.client_secret).build_token( scopes, expires_in, aud=client.client_id) return EdxRestApiClient(api_config.internal_api_url, jwt=jwt)
def course_discovery_api_client(user): """ Returns a Course Discovery API client setup with authentication for the specified user. """ scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) return EdxRestApiClient(settings.COURSE_CATALOG_API_URL, jwt=jwt)
def _build_jwt_response_from_access_token_response(self, request, response): """ Builds the content of the response, including the JWT token. """ client_id = self._get_client_id(request) adapter = self.get_adapter(request) expires_in, scopes, user = self._decompose_access_token_response( adapter, response) issuer, secret, audience, filters, is_client_restricted = self._get_client_specific_claims( client_id, adapter) content = { 'access_token': JwtBuilder( user, secret=secret, issuer=issuer, ).build_token( scopes, expires_in, aud=audience, additional_claims={ 'filters': filters, 'is_restricted': is_client_restricted, }, ), 'expires_in': expires_in, 'token_type': 'JWT', 'scope': ' '.join(scopes), } return json.dumps(content)
def create_catalog_api_client(user, catalog_integration): """Returns an API client which can be used to make catalog API requests.""" scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) return EdxRestApiClient(catalog_integration.internal_api_url, jwt=jwt)
def test_user_profile_missing(self): """ Verify that token construction succeeds if the UserProfile is missing. """ self.profile.delete() token = JwtBuilder(self.user).build_token(expires_in=self.expires_in) self.assert_valid_jwt_access_token(token, self.user, self.scopes)
def __init__(self, user): """ Initialize an authenticated Enterprise service API client by using the provided user. """ jwt = JwtBuilder(user).build_token([]) self.client = EdxRestApiClient(configuration_helpers.get_value( 'ENTERPRISE_API_URL', settings.ENTERPRISE_API_URL), jwt=jwt)
def test_user_profile_missing(self): """ Verify that token construction succeeds if the UserProfile is missing. """ self.profile.delete() # pylint: disable=no-member scopes = ['profile'] token = JwtBuilder(self.user).build_token(scopes, self.expires_in) self.assert_valid_jwt_access_token(token, self.user, scopes)
def test_jwt_auth(self): """ verify the endpoints JWT authentication. """ scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION token = JwtBuilder(self.user).build_token(scopes, expires_in) headers = {'HTTP_AUTHORIZATION': 'JWT ' + token} self.client.logout() response = self.client.get(self.path, **headers) self.assertEqual(response.status_code, 200)
def ecommerce_api_client(user, session=None, token_expiration=None): """ Returns an E-Commerce API client setup with authentication for the specified user. """ claims = {'tracking_context': create_tracking_context(user)} jwt = JwtBuilder(user).build_token(['email', 'profile'], expires_in=token_expiration, additional_claims=claims) return EdxRestApiClient( configuration_helpers.get_value('ECOMMERCE_API_URL', settings.ECOMMERCE_API_URL), jwt=jwt, session=session )
def __init__(self): """ Initialize an Enterprise service API client, authenticated using the Enterprise worker username. """ self.user = User.objects.get( username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME) jwt = JwtBuilder(self.user).build_token([]) self.client = EdxRestApiClient(configuration_helpers.get_value( 'ENTERPRISE_API_URL', settings.ENTERPRISE_API_URL), jwt=jwt)
def test_override_secret_and_audience(self): """ Verify that the signing key and audience can be overridden. """ secret = 'avoid-this' audience = 'avoid-this-too' scopes = [] token = JwtBuilder(self.user, secret=secret).build_token(scopes, self.expires_in, aud=audience) jwt.decode(token, secret, audience=audience)
def create_video_pipeline_api_client(user, api_url): """ Returns an API client which can be used to make Video Pipeline API requests. Arguments: user(User): A requesting user. api_url(unicode): It is video pipeline's API URL. """ jwt_token = JwtBuilder(user).build_token( scopes=[], expires_in=settings.OAUTH_ID_TOKEN_EXPIRATION) return EdxRestApiClient(api_url, jwt=jwt_token)
def create_catalog_api_client(user, site=None): """Returns an API client which can be used to make Catalog API requests.""" scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) if site: url = site.configuration.get_value('COURSE_CATALOG_API_URL') else: url = CatalogIntegration.current().get_internal_api_url() return EdxRestApiClient(url, jwt=jwt)
def __init__(self, user): """ Initialize an authenticated Consent service API client by using the provided user. """ jwt = JwtBuilder(user).build_token([]) url = configuration_helpers.get_value('ENTERPRISE_CONSENT_API_URL', settings.ENTERPRISE_CONSENT_API_URL) self.client = EdxRestApiClient( url, jwt=jwt, append_slash=False, ) self.consent_endpoint = self.client.data_sharing_consent
def __init__(self): """ Initialize a consent service API client, authenticated using the Enterprise worker username. """ self.user = User.objects.get(username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME) jwt = JwtBuilder(self.user).build_token([]) url = configuration_helpers.get_value('ENTERPRISE_CONSENT_API_URL', settings.ENTERPRISE_CONSENT_API_URL) self.client = EdxRestApiClient( url, jwt=jwt, append_slash=False, ) self.consent_endpoint = self.client.data_sharing_consent
def course_discovery_api_client(user): """ Return a Course Discovery API client setup with authentication for the specified user. """ if JwtBuilder is None: raise NotConnectedToOpenEdX( _("To get a Catalog API client, this package must be " "installed in an Open edX environment.")) scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) return EdxRestApiClient(settings.COURSE_CATALOG_API_URL, jwt=jwt)
def dispatch(self, request, *args, **kwargs): response = super(AccessTokenView, self).dispatch(request, *args, **kwargs) if response.status_code == 200 and request.POST.get('token_type', '').lower() == 'jwt': expires_in, scopes, user = self._decompose_access_token_response(request, response) content = { 'access_token': JwtBuilder(user).build_token(scopes, expires_in), 'expires_in': expires_in, 'token_type': 'JWT', 'scope': ' '.join(scopes), } response.content = json.dumps(content) return response
def get_api_client(api_config, user): """ Create and configure an API client for authenticated HTTP requests. Args: api_config: CredentialsApiConfig object user: User object as whom to authenticate to the API Returns: EdxRestApiClient """ scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) return EdxRestApiClient(api_config.internal_api_url, jwt=jwt)
def __init__(self): """ Initialize an authenticated Journals service API client by using the provided user. """ try: self.user = self.get_journals_worker() except ObjectDoesNotExist: error = 'Unable to retrieve {} service user'.format( JOURNAL_WORKER_USERNAME) LOGGER.error(error) raise ValueError(error) jwt = JwtBuilder(self.user).build_token(['email', 'profile'], 16000) self.client = EdxRestApiClient(configuration_helpers.get_value( 'JOURNALS_API_URL', settings.JOURNALS_API_URL), jwt=jwt)
def get_edxnotes_id_token(user): """ Returns generated ID Token for edxnotes. """ # TODO: Use the system's JWT_AUDIENCE and JWT_SECRET_KEY instead of client ID and name. try: client = Client.objects.get(name=CLIENT_NAME) except Client.DoesNotExist: raise ImproperlyConfigured( 'OAuth2 Client with name [{}] does not exist.'.format(CLIENT_NAME)) scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user, secret=client.client_secret).build_token( scopes, expires_in, aud=client.client_id) return jwt
def connect(self): """ Connect to the REST API, authenticating with a JWT for the current user. """ if JwtBuilder is None: raise NotConnectedToOpenEdX( "This package must be installed in an OpenEdX environment.") now = int(time()) scopes = ['profile', 'email'] jwt = JwtBuilder(self.user).build_token(scopes, self.expires_in) self.client = EdxRestApiClient( self.API_BASE_URL, append_slash=self.APPEND_SLASH, jwt=jwt, ) self.expires_at = now + self.expires_in
def _get_jwt_builder(self, user, is_client_restricted): """ Creates and returns a JWTBuilder object for creating JWTs. """ # If JWT scope enforcement is enabled, we need to sign tokens # given to restricted applications with a key that # other IDAs do not have access to. This prevents restricted # applications from getting access to API endpoints available # on other IDAs which have not yet been protected with the # scope-related DRF permission classes. Once all endpoints have # been protected, we can enable all IDAs to use the same new # (asymmetric) key. # TODO: ARCH-162 use_asymmetric_key = ENFORCE_JWT_SCOPES.is_enabled( ) and is_client_restricted return JwtBuilder( user, asymmetric=use_asymmetric_key, )
def get_credentials_api_client(user, org=None): """ Returns an authenticated Credentials API client. Arguments: user (User): The user to authenticate as when requesting credentials. org (str): Optional organization to look up the site config for, rather than the current request """ scopes = ['email', 'profile'] expires_in = settings.OAUTH_ID_TOKEN_EXPIRATION jwt = JwtBuilder(user).build_token(scopes, expires_in) 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 EdxRestApiClient(url, jwt=jwt)