Exemple #1
0
 def __init__(self, client_id=None, client_secret=None, **kwargs):
     """
     Initialize REST backend.
     client_id: provided by backend service
     client_secret: provided by backend service
     """
     ProctoringBackendProvider.__init__(self)
     self.client_id = client_id
     self.client_secret = client_secret
     self.default_rules = None
     for key, value in kwargs.items():
         setattr(self, key, value)
     self.session = OAuthAPIClient(self.base_url, self.client_id,
                                   self.client_secret)
Exemple #2
0
    def api_client(self):
        """
        Returns a requests client for this site's service user.

        This client is authenticated with the configured oauth settings and automatically cached.

        Returns:
            requests.Session: API client
        """
        return OAuthAPIClient(
            settings.BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
            settings.BACKEND_SERVICE_EDX_OAUTH2_KEY,
            settings.BACKEND_SERVICE_EDX_OAUTH2_SECRET,
        )
Exemple #3
0
    def test_access_token_request_timeout_wiring2(self,
                                                  mock_access_token_post):
        mock_access_token_post.return_value.json.return_value = {
            'access_token': 'token',
            'expires_in': 1000
        }

        timeout_override = (6.1, 2)
        client = OAuthAPIClient(self.base_url,
                                self.client_id,
                                self.client_secret,
                                timeout=timeout_override)
        client._ensure_authentication()  # pylint: disable=protected-access

        assert mock_access_token_post.call_args.kwargs[
            'timeout'] == timeout_override
 def test_automatic_auth(self):
     """
     Test that the JWT token is automatically set
     """
     session = OAuthAPIClient(self.base_url, self.client_id,
                              self.client_secret)
     self._mock_auth_api(self.base_url + '/oauth2/access_token', 200, {
         'access_token': 'abcd',
         'expires_in': 60
     })
     self._mock_auth_api(self.base_url + '/endpoint', 200, {'status': 'ok'})
     response = session.post(self.base_url + '/endpoint',
                             data={'test': 'ok'})
     self.assertIn('client_id=%s' % self.client_id,
                   responses.calls[0].request.body)
     self.assertEqual(session.auth.token, 'abcd')
     self.assertEqual(response.json()['status'], 'ok')
Exemple #5
0
def _request_financial_assistance(method, url, params=None, data=None):
    """
    An internal function containing common functionality among financial assistance utility function to call
    edx-financial-assistance backend with appropriate method, url, params and data.
    """
    financial_assistance_configuration = FinancialAssistanceConfiguration.current()
    if financial_assistance_configuration.enabled:
        oauth_application = Application.objects.get(user=financial_assistance_configuration.get_service_user())
        client = OAuthAPIClient(
            settings.LMS_ROOT_URL,
            oauth_application.client_id,
            oauth_application.client_secret
        )
        return client.request(
            method, f"{financial_assistance_configuration.api_base_url}{url}", params=params, data=data
        )
    else:
        return False, 'Financial Assistance configuration is not enabled'
Exemple #6
0
def enroll_in_course(course_id, email, send_email=True):
    """
    Auto-enroll email in course.

    Uses the bulk enrollment API, defined in lms/djangoapps/bulk_enroll
    """

    # Raises ValidationError if invalid
    validate_email(email)

    client = OAuthAPIClient(
        settings.SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT,
        settings.SOCIAL_AUTH_EDX_OAUTH2_KEY,
        settings.SOCIAL_AUTH_EDX_OAUTH2_SECRET,
    )

    bulk_enroll_url = EDX_BULK_ENROLLMENT_API_PATH % settings.SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT  # noqa: E501

    # The bulk enrollment API allows us to enroll multiple identifiers
    # at once, using a comma-separated list for the courses and
    # identifiers parameters. We deliberately want to process
    # enrollments one by one, so we use a single request for each
    # course/identifier combination.
    request_params = {
        "auto_enroll": True,
        "email_students": send_email,
        "action": "enroll",
        "courses": course_id,
        "identifiers": email,
    }

    logger.debug("Sending POST request "
                 "to %s with parameters %s" %
                 (bulk_enroll_url, request_params))
    response = client.post(bulk_enroll_url, request_params)

    # Throw an exception if we get anything other than HTTP 200 back
    # from the API (the only other status we might be getting back
    # from the bulk enrollment API is HTTP 400).
    response.raise_for_status()

    # If all is well, log the response at the debug level.
    logger.debug("Received response from %s: %s " %
                 (bulk_enroll_url, response.json()))
Exemple #7
0
    def test_shared_client_credential_jwt_access_token(self):
        """
        Test that get_and_cache_jwt_oauth_access_token returns the same access token used by the OAuthAPIClient.
        """
        body = {'access_token': "my-token", 'expires_in': 1000}
        now = datetime.datetime.utcnow()
        expected_return = ('my-token', now + datetime.timedelta(seconds=1000))

        with freeze_time(now):
            self._mock_auth_api(OAUTH_URL, 200, body=body)
            actual_return = EdxRestApiClient.get_and_cache_jwt_oauth_access_token(
                OAUTH_URL, 'client_id', 'client_secret')
        self.assertEqual(actual_return, expected_return)
        self.assertEqual(len(responses.calls), 1)

        # ensure OAuthAPIClient uses the same cached auth token without re-requesting the token from the server
        oauth_client = OAuthAPIClient(OAUTH_URL, 'client_id', 'client_secret')
        self._mock_auth_api(URL, 200, {'status': 'ok'})
        oauth_client.post(URL, data={'test': 'ok'})
        self.assertEqual(oauth_client.auth.token, actual_return[0])
        self.assertEqual(len(responses.calls), 2)
        self.assertEqual(URL, responses.calls[1][0].url)
Exemple #8
0
    def test_automatic_token_refresh(self):
        """
        Test that the JWT token is automatically refreshed
        """
        tokens = ['cred2', 'cred1']

        def auth_callback(request):
            resp = {'expires_in': 60}
            if 'grant_type=client_credentials' in request.body:
                resp['access_token'] = tokens.pop()
            return (200, {}, json.dumps(resp))

        responses.add_callback(
            responses.POST,
            self.base_url + '/oauth2/access_token',
            callback=auth_callback,
            content_type='application/json',
        )

        client_session = OAuthAPIClient(self.base_url, self.client_id,
                                        self.client_secret)
        self._mock_auth_api(self.base_url + '/endpoint', 200, {'status': 'ok'})
        response = client_session.post(self.base_url + '/endpoint',
                                       data={'test': 'ok'})
        first_call_datetime = datetime.datetime.utcnow()
        self.assertEqual(client_session.auth.token, 'cred1')
        self.assertEqual(response.json()['status'], 'ok')
        # after only 30 seconds should still use the cached token
        with freeze_time(first_call_datetime + datetime.timedelta(seconds=30)):
            response = client_session.post(self.base_url + '/endpoint',
                                           data={'test': 'ok'})
            self.assertEqual(client_session.auth.token, 'cred1')
        # after just under a minute, should request a new token
        # - expires early due to ACCESS_TOKEN_EXPIRED_THRESHOLD_SECONDS
        with freeze_time(first_call_datetime + datetime.timedelta(seconds=56)):
            response = client_session.post(self.base_url + '/endpoint',
                                           data={'test': 'ok'})
            self.assertEqual(client_session.auth.token, 'cred2')
    def update_val(self, image_keys):
        """
        Update a course video in edxval database for auto generated images.
        """
        if len(image_keys) > 0:

            for course_id in self.video_object.course_url:
                data = {
                    'course_id': course_id,
                    'edx_video_id': self.video_object.val_id,
                    'generated_images': image_keys
                }

                client = OAuthAPIClient(self.settings['oauth2_provider_url'],
                                        self.settings['oauth2_client_id'],
                                        self.settings['oauth2_client_secret'])

                response = client.request(
                    'POST', self.settings['val_video_images_url'], json=data)

                if not response.ok:
                    logger.error(': {id} {message}'.format(
                        id=self.video_object.val_id, message=response.content))
 def __init__(self, video_proto, val_status, **kwargs):
     """VAL Data"""
     self.val_status = val_status
     self.platform_course_url = kwargs.get('platform_course_url', [])
     """VEDA Data"""
     self.video_proto = video_proto
     self.video_object = kwargs.get('video_object', None)
     self.encode_profile = kwargs.get('encode_profile', None)
     """if sending urls"""
     self.endpoint_url = kwargs.get('endpoint_url', None)
     self.encode_data = []
     self.val_profile = None
     """Generated"""
     self.val_data = None
     self.headers = None
     """Credentials"""
     self.auth_dict = kwargs.get('CONFIG_DATA', self._AUTH())
     self.oauth2_provider_url = self.auth_dict['oauth2_provider_url']
     self.oauth2_client_id = self.auth_dict['oauth2_client_id']
     self.oauth2_client_secret = self.auth_dict['oauth2_client_secret']
     self.oauth2_client = OAuthAPIClient(
         self.auth_dict['oauth2_provider_url'],
         self.auth_dict['oauth2_client_id'],
         self.auth_dict['oauth2_client_secret'])
Exemple #11
0
def load_course_structure_from_CMS(course_code):
    """Uses an OAuthAPIClient to recover the course structure from the CMS backend

    Arguments:
        course_code string course-v1:coursename

    Returns:
        JSON text response with course tree
    """
    client = OAuthAPIClient(settings.BACKEND_LMS_BASE_URL,
                            settings.BACKEND_SERVICE_EDX_OAUTH2_KEY,
                            settings.BACKEND_SERVICE_EDX_OAUTH2_SECRET)
    client.headers.update({
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Content-Type": "application/json"
    })

    response = client.get("{}/course/{}?format=concise".format(
        settings.BACKEND_CMS_BASE_URL, course_code))

    if response.status_code != 200:
        raise Exception("Request to CMS failed for {}".format(course_code),
                        str(response.text))
    return response.text
Exemple #12
0
    parser = argparse.ArgumentParser(
        description='run the mockprock server',
        epilog=
        'Retrieve the mockprock client id and secret from the LMS Django admin, and start the server with those arguments'
    )
    parser.add_argument("client_id",
                        type=str,
                        help="oauth client id",
                        nargs="?")
    parser.add_argument("client_secret",
                        type=str,
                        help="oauth client secret",
                        nargs="?")
    parser.add_argument('-l',
                        dest='lms_host',
                        type=str,
                        help='LMS host',
                        default='http://host.docker.internal:18000')
    args = parser.parse_args()

    if not (args.client_id and args.client_secret):
        parser.print_help()
        time.sleep(2)
        import webbrowser
        webbrowser.open('%s/admin/oauth2_provider/application/' %
                        args.lms_host)
        sys.exit(1)
    app.client = OAuthAPIClient(args.lms_host, args.client_id,
                                args.client_secret)
    app.run(host='0.0.0.0', port=11136)
Exemple #13
0
    def send_val_data(self):
        """
        VAL is very tetchy -- it needs a great deal of specific info or it will fail
        """
        '''
        sending_data = {
            encoded_videos = [{
                url="https://testurl.mp4",
                file_size=8499040,
                bitrate=131,
                profile="override",
                }, {...},],
            client_video_id = "This is a VEDA-VAL Test",
            courses = [ "TEST", "..." ],
            duration = 517.82,
            edx_video_id = "TESTID",
            status = "transcode_active"
            }
        ## "POST" for new objects to 'video' root url
        ## "PUT" for extant objects to video/id --
            cannot send duplicate course records
        '''

        # in case non-studio side upload
        if self.VideoObject.val_id is None or len(
                self.VideoObject.val_id) == 0:
            self.VideoObject.val_id = self.VideoObject.veda_id

        val_data = {
            'client_video_id': self.VideoObject.val_id,
            'duration': self.VideoObject.mezz_duration,
            'edx_video_id': self.VideoObject.val_id,
        }

        if not isinstance(self.VideoObject.course_url, list):
            self.VideoObject.course_url = [self.VideoObject.course_url]

        client = OAuthAPIClient(settings['oauth2_provider_url'],
                                settings['oauth2_client_id'],
                                settings['oauth2_client_secret'])
        r1 = client.request(
            'GET', '/'.join(
                (settings['val_api_url'], self.VideoObject.val_id, '')))

        if r1.status_code != 200 and r1.status_code != 404:
            # Total API Failure
            logger.error('VAL Communication error %d', r1.status_code)
            return None

        if r1.status_code == 404:
            # Generate new VAL ID (shouldn't happen, but whatever)
            val_data['encoded_videos'] = []
            val_data['courses'] = self.VideoObject.course_url
            val_data['status'] = self.val_video_status

            # Final Connection
            r2 = client.request('POST', settings['val_api_url'], json=val_data)
            if r2.status_code > 299:
                logger.error('VAL POST error %d', r2.status_code)
                return None

        elif r1.status_code == 200:
            # ID is previously extant
            val_api_return = ast.literal_eval(r1.text)
            # extract course ids, courses will be a list of dicts, [{'course_id': 'image_name'}]
            course_ids = reduce(operator.concat,
                                (list(d.keys())
                                 for d in val_api_return['courses']))

            # VAL will not allow duped studio urls to be sent, so
            # we must scrub the data

            for course_id in self.VideoObject.course_url:
                if course_id in course_ids:
                    self.VideoObject.course_url.remove(course_id)

            val_data['courses'] = self.VideoObject.course_url

            # Double check for profiles in case of overwrite
            val_data['encoded_videos'] = []
            # add back in the encodes
            for e in val_api_return['encoded_videos']:
                val_data['encoded_videos'].append(e)

            # Determine Status
            val_data['status'] = self.val_video_status

            # Make Request, finally
            r2 = client.request('PUT',
                                '/'.join((settings['val_api_url'],
                                          self.VideoObject.val_id)),
                                json=val_data)

            if r2.status_code > 299:
                logger.error('VAL PUT error %d', r2.status_code)
                return None
Exemple #14
0
 def test_get_jwt_access_token(self):
     token = 'abcd'
     self._mock_auth_api(self.base_url + '/oauth2/access_token', 200, {'access_token': token, 'expires_in': 60})
     client = OAuthAPIClient(self.base_url, self.client_id, self.client_secret)
     access_token = client.get_jwt_access_token()
     self.assertEqual(access_token, token)
Exemple #15
0
 def __init__(self):
     assert self.api_url_root
     self._client = OAuthAPIClient(OAUTH_ACCESS_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
Exemple #16
0
 def __init__(self) -> None:
     self.client = OAuthAPIClient(
         settings.SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT.strip("/"),
         self.oauth2_client_id,
         self.oauth2_client_secret,
     )
Exemple #17
0
def enroll_in_course(
        course_id,
        email,
        send_email=settings.WEBHOOK_RECEIVER_SEND_ENROLLMENT_EMAIL,
        auto_enroll=settings.WEBHOOK_RECEIVER_AUTO_ENROLL):
    """
    Auto-enroll email in course.

    Uses the bulk enrollment API, defined in lms/djangoapps/bulk_enroll
    """

    # Raises ValidationError if invalid
    validate_email(email)

    client = OAuthAPIClient(
        settings.WEBHOOK_RECEIVER_LMS_BASE_URL,
        settings.WEBHOOK_RECEIVER_EDX_OAUTH2_KEY,
        settings.WEBHOOK_RECEIVER_EDX_OAUTH2_SECRET,
    )

    bulk_enroll_url = EDX_BULK_ENROLLMENT_API_PATH % settings.WEBHOOK_RECEIVER_LMS_BASE_URL  # noqa: E501

    # The bulk enrollment API allows us to enroll multiple identifiers
    # at once, using a comma-separated list for the courses and
    # identifiers parameters. We deliberately want to process
    # enrollments one by one, so we use a single request for each
    # course/identifier combination.
    request_params = {
        "auto_enroll": auto_enroll,
        "email_students": send_email,
        "action": "enroll",
        "courses": course_id,
        "identifiers": email,
    }

    logger.debug("Sending POST request "
                 "to %s with parameters %s" %
                 (bulk_enroll_url, request_params))
    response = client.post(bulk_enroll_url, request_params)

    # Throw an exception if we get any error back from the API.
    # Apart from an HTTP 200, we might also get:
    #
    # HTTP 400: if we've sent a malformed request (for example, one
    #           with a course ID in a format that Open edX can't
    #           parse)
    # HTTP 401: if our authentication token has expired
    # HTTP 403: if our auth token is linked to a user ID that lacks
    #           staff credentials in one of the courses we want to
    #           enroll the learner in
    # HTTP 404: if we've specified a course ID that does not exist
    #           (although it does follow the format that Open edX expects)
    # HTTP 500: in case of a server-side issue
    if response.status_code >= 400:
        logger.error("POST request to %s with parameters %s "
                     "returned HTTP %s" %
                     (bulk_enroll_url, request_params, response.status_code))
    response.raise_for_status()

    # If all is well, log the response at the debug level.
    logger.debug("Received response from %s: %s " %
                 (bulk_enroll_url, response.json()))
Exemple #18
0
    def lms_api_client(self):
        if not self.lms_url:
            return None

        return OAuthAPIClient(self.lms_url.strip('/'), self.oidc_key,
                              self.oidc_secret)