Example #1
0
    def refresh_all(cls, access_token):
        """
        Refresh all course data.

        Args:
            access_token (str): OAuth access token

        Returns:
            None
        """
        client = EdxRestApiClient(settings.ECOMMERCE_API_URL, oauth_access_token=access_token)

        logger.info('Refreshing course data from %s....', settings.ECOMMERCE_API_URL)

        count = None
        page = 1
        while page:
            response = client.courses().get(include_products=True, page=page, page_size=50)
            count = response['count']
            results = response['results']
            logger.info('Retrieved %d courses...', len(results))

            if response['next']:
                page += 1
            else:
                page = None

            for body in results:
                Course(body['id'], body).save()

        logger.info('Retrieved %d courses.', count)
Example #2
0
    def is_eligible_for_credit(self, course_key):
        """
        Check if a user is eligible for a credit course.
        Calls the LMS eligibility API endpoint and sends the username and course key
        query parameters and returns eligibility details for the user and course combination.

        Args:
            course_key (string): The course key for which the eligibility is checked for.

        Returns:
            A list that contains eligibility information, or empty if user is not eligible.

        Raises:
            ConnectionError, SlumberBaseException and Timeout for failures in establishing a
            connection with the LMS eligibility API endpoint.
        """
        query_strings = {
            'username': self.username,
            'course_key': course_key
        }
        try:
            api = EdxRestApiClient(
                get_lms_url('api/credit/v1/'),
                oauth_access_token=self.access_token
            )
            response = api.eligibility().get(**query_strings)
        except (ConnectionError, SlumberBaseException, Timeout):  # pragma: no cover
            log.exception(
                'Failed to retrieve eligibility details for [%s] in course [%s]',
                self.username,
                course_key
            )
            raise
        return response
Example #3
0
    def account_details(self, request):
        """ Returns the account details from LMS.

        Args:
            request (WSGIRequest): The request from which the LMS account API endpoint is created.

        Returns:
            A dictionary of account details.

        Raises:
            ConnectionError, SlumberBaseException and Timeout for failures in establishing a
            connection with the LMS account API endpoint.
        """
        try:
            api = EdxRestApiClient(
                request.site.siteconfiguration.build_lms_url('/api/user/v1'),
                append_slash=False,
                jwt=request.site.siteconfiguration.access_token
            )
            response = api.accounts(self.username).get()
            return response
        except (ConnectionError, SlumberBaseException, Timeout):
            log.exception(
                'Failed to retrieve account details for [%s]',
                self.username
            )
            raise
Example #4
0
    def get_context_data(self, **kwargs):
        context = super(CouponOfferView, self).get_context_data(**kwargs)

        code = self.request.GET.get('code', None)
        if code is not None:
            voucher, product = get_voucher(code=code)
            valid_voucher, msg = voucher_is_valid(voucher, product, self.request)
            if valid_voucher:
                api = EdxRestApiClient(
                    get_lms_url('api/courses/v1/'),
                )
                try:
                    course = api.courses(product.course_id).get()
                except SlumberHttpBaseException as e:
                    logger.exception('Could not get course information. [%s]', e)
                    return {
                        'error': _('Could not get course information. [{error}]'.format(error=e))
                    }

                course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
                stock_records = voucher.offers.first().benefit.range.catalog.stock_records.first()
                context.update({
                    'course': course,
                    'code': code,
                    'price': stock_records.price_excl_tax,
                    'verified': (product.attr.certificate_type is 'verified')
                })
                return context
            return {
                'error': msg
            }
        return {
            'error': _('This coupon code is invalid.')
        }
Example #5
0
    def refresh_all_course_api_data(cls, access_token):
        course_api_url = settings.COURSES_API_URL
        client = EdxRestApiClient(course_api_url, oauth_access_token=access_token)

        count = None
        page = 1

        logger.info('Refreshing course api data from %s....', course_api_url)

        while page:
            # TODO Update API to not require username?
            response = client.courses().get(page=page, page_size=50, username='******')
            count = response['pagination']['count']
            results = response['results']
            logger.info('Retrieved %d courses...', len(results))

            if response['pagination']['next']:
                page += 1
            else:
                page = None

            for body in results:
                Course(body['id']).update(body)

        logger.info('Retrieved %d courses from %s.', count, course_api_url)
Example #6
0
    def _delete_program(self, program_id, jwt_token):
        """ With the JWT token, hit the program details URL with the patch to set the
            program status to "deleted". This is the delete program step """

        url = '{0}/api/v1/'.format(PROGRAMS_URL_ROOT)
        delete_client = EdxRestApiClient(url, jwt=jwt_token)
        deleted_program = delete_client.programs(program_id).patch({'status': 'deleted'})
        # tell the caller wither the delete is successful or not.
        return deleted_program['status'] == 'deleted'
Example #7
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        if code:
            voucher, __ = get_voucher_from_code(code=code)
        else:
            voucher = None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
            course_key = product.attr.course_key

            api = EdxRestApiClient(
                get_lms_enrollment_base_api_url(),
                oauth_access_token=request.user.access_token,
                append_slash=False
            )
            logger.debug(
                'Getting enrollment information for [%s] in [%s].',
                request.user.username,
                course_key
            )
            status = api.enrollment(','.join([request.user.username, course_key])).get()
            username = request.user.username
            seat_type = mode_for_seat(product)
            if status and status.get('mode') == seat_type and status.get('is_active'):
                logger.warning(
                    'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                    username,
                    seat_type,
                    course_key
                )
                return HttpResponseBadRequest(_('You are already enrolled in {course}.').format(
                    course=product.course.name))
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.').format(sku=sku))
        except (ConnectionError, SlumberBaseException, Timeout) as ex:
            logger.exception(
                'Failed to retrieve enrollment details for [%s] in course [%s], Because of [%s]',
                request.user.username,
                course_key,
                ex,
            )
            return HttpResponseBadRequest(_('An error occurred while retrieving enrollment details. Please try again.'))
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            return HttpResponseBadRequest(_('Product [{product}] not available to buy.').format(product=product.title))

        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Example #8
0
def get_course_info_from_lms(course_key):
    """ Get course information from LMS via the course api and cache """
    api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
    cache_key = 'courses_api_detail_{}'.format(course_key)
    cache_hash = hashlib.md5(cache_key).hexdigest()
    course = cache.get(cache_hash)
    if not course:  # pragma: no cover
        course = api.courses(course_key).get()
        cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT)
    return course
Example #9
0
 def get_context_data(self, **kwargs):
     context = super(CouponOfferView, self).get_context_data(**kwargs)
     footer = get_lms_footer()
     code = self.request.GET.get('code', None)
     if code is not None:
         voucher, product = get_voucher_from_code(code=code)
         valid_voucher, msg = voucher_is_valid(voucher, product, self.request)
         if valid_voucher:
             api = EdxRestApiClient(
                 get_lms_url('api/courses/v1/'),
             )
             try:
                 course = api.courses(product.course_id).get()
             except SlumberHttpBaseException as e:
                 logger.exception('Could not get course information. [%s]', e)
                 return {
                     'error': _('Could not get course information. [{error}]'.format(error=e)),
                     'footer': footer
                 }
             course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
             benefit = voucher.offers.first().benefit
             stock_record = benefit.range.catalog.stock_records.first()
             price = stock_record.price_excl_tax
             context.update(get_voucher_discount_info(benefit, price))
             if benefit.type == 'Percentage':
                 new_price = price - (price * (benefit.value / 100))
             else:
                 new_price = price - benefit.value
                 if new_price < 0:
                     new_price = Decimal(0)
             context.update({
                 'benefit': benefit,
                 'course': course,
                 'code': code,
                 'is_discount_value_percentage': benefit.type == 'Percentage',
                 'is_enrollment_code': benefit.type == Benefit.PERCENTAGE and benefit.value == 100.00,
                 'discount_value': "%.2f" % (price - new_price),
                 'price': price,
                 'new_price': "%.2f" % new_price,
                 'verified': (product.attr.certificate_type == 'verified'),
                 'verification_deadline': product.course.verification_deadline,
                 'footer': footer
             })
             return context
         return {
             'error': msg,
             'footer': footer
         }
     return {
         'error': _('This coupon code is invalid.'),
         'footer': footer
     }
class CatalogApiService(object):
    """The service to interface with edX catalog API"""

    def __init__(self, access_token, oauth_host, oauth_key, oauth_secret, api_url_root):
        self.access_token = access_token
        if not access_token:
            logger.info('No access token provided. Retrieving access token using client_credential flow...')
            try:
                self.access_token, expires = EdxRestApiClient.get_oauth_access_token(
                    '{root}/access_token'.format(root=oauth_host),
                    oauth_key,
                    oauth_secret, token_type='jwt'
                )
            except Exception:
                logger.exception('No access token provided or acquired through client_credential flow.')
                raise

            logger.info('Token retrieved: %s', access_token)

        self.api_client = EdxRestApiClient(api_url_root, jwt=self.access_token)
        self._programs_dictionary = {}

    def _get_resource_from_api(self, api_endpoint, page_size, **kwargs):
        page = 0
        results = []

        while page >= 0:
            response = api_endpoint.get(limit=page_size, offset=(page * page_size), **kwargs)
            if response.get('next'):
                page += 1
            else:
                page = -1
            results.extend(response.get('results'))

        return results

    def get_courses(self):
        logger.debug('Get Courses called')
        return self._get_resource_from_api(self.api_client.courses(), COURSES_PAGE_SIZE, marketable=1)

    def get_program_dictionary(self):
        if not self._programs_dictionary:
            program_array = self._get_resource_from_api(
                self.api_client.programs(),
                PROGRAMS_PAGE_SIZE,
                marketable=1,
                published_course_runs_only=1
            )
            for program in program_array:
                self._programs_dictionary[program['uuid']] = program
        return self._programs_dictionary
Example #11
0
    def _publish_creditcourse(self, course_id, access_token):
        """Creates or updates a CreditCourse object on the LMS."""

        api = EdxRestApiClient(
            get_lms_url('api/credit/v1/'),
            oauth_access_token=access_token,
            timeout=self.timeout
        )

        data = {
            'course_key': course_id,
            'enabled': True
        }

        api.courses(course_id).put(data)
    def _get_courses_enrollment_info(self):
        """
        Retrieve the enrollment information for all the courses.

        Returns:
            Dictionary representing the key-value pair (course_key, enrollment_end) of course.
        """
        def _parse_response(api_response):
            response_data = api_response.get('results', [])

            # Map course_id with enrollment end date.
            courses_enrollment = dict(
                (course_info['course_id'], course_info['enrollment_end'])
                for course_info in response_data
            )
            return courses_enrollment, api_response['pagination'].get('next', None)

        querystring = {'page_size': 50}
        api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
        course_enrollments = {}

        page = 0
        throttling_attempts = 0
        next_page = True
        while next_page:
            page += 1
            querystring['page'] = page
            try:
                response = api.courses().get(**querystring)
                throttling_attempts = 0
            except HttpClientError as exc:
                # this is a known limitation; If we get HTTP429, we need to pause execution for a few seconds
                # before re-requesting the data. raise any other errors
                if exc.response.status_code == 429 and throttling_attempts < self.max_tries:
                    logger.warning(
                        'API calls are being rate-limited. Waiting for [%d] seconds before retrying...',
                        self.pause_time
                    )
                    time.sleep(self.pause_time)
                    page -= 1
                    throttling_attempts += 1
                    logger.info('Retrying [%d]...', throttling_attempts)
                    continue
                else:
                    raise
            enrollment_info, next_page = _parse_response(response)
            course_enrollments.update(enrollment_info)
        return course_enrollments
Example #13
0
    def access_token(self):
        """ Returns an access token for this site's service user.

        The access token is retrieved using the current site's OAuth credentials and the client credentials grant.
        The token is cached for the lifetime of the token, as specified by the OAuth provider's response. The token
        type is JWT.

        Returns:
            str: JWT access token
        """
        key = 'siteconfiguration_access_token_{}'.format(self.id)
        access_token = cache.get(key)

        # pylint: disable=unsubscriptable-object
        if not access_token:
            url = '{root}/access_token'.format(root=self.oauth2_provider_url)
            access_token, expiration_datetime = EdxRestApiClient.get_oauth_access_token(
                url,
                self.oauth_settings['SOCIAL_AUTH_EDX_OIDC_KEY'],
                self.oauth_settings['SOCIAL_AUTH_EDX_OIDC_SECRET'],
                token_type='jwt'
            )

            expires = (expiration_datetime - datetime.datetime.utcnow()).seconds
            cache.set(key, access_token, expires)

        return access_token
Example #14
0
class DiscoveryApiClient(object):
    """
    Class for interacting with the discovery service journals endpoint
    """
    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_journals(self, orgs):
        """
        get_journals from discovery, filter on orgs is supplied
        """
        try:
            if orgs:
                response = self.client.journals.get(orgs=','.join(orgs), status='active')
            else:
                response = self.client.journals.get(status='active')
            LOGGER.debug('response is type=%s', type(response))
            return response.get('results')
        except (HttpClientError, HttpServerError) as err:
            LOGGER.exception(
                'Failed to get journals from discovery-service [%s]',
                err.content
            )
            return []

    def get_journal_bundles(self, uuid=''):
        """
        get_journal_bundles from discovery on the base of uuid (optional)
        """
        try:
            response = self.client.journal_bundles(uuid).get()
        except (HttpClientError, HttpServerError) as err:
            LOGGER.exception(
                'Failed to get journal bundles from discovery-service [%s]',
                err.content
            )
            return []
        return [response] if uuid else response.get('results')
Example #15
0
    def refresh(cls, course_id, access_token):
        """
        Refresh the course data from the raw data sources.

        Args:
            course_id (str): Course ID
            access_token (str): OAuth access token

        Returns:
            Course
        """
        client = EdxRestApiClient(settings.ECOMMERCE_API_URL, oauth_access_token=access_token)
        body = client.courses(course_id).get(include_products=True)
        course = Course(course_id, body)
        course.save()
        return course
    def __init__(self, access_token, oauth_host, oauth_key, oauth_secret, api_url_root):
        self.access_token = access_token
        if not access_token:
            logger.info('No access token provided. Retrieving access token using client_credential flow...')
            try:
                self.access_token, expires = EdxRestApiClient.get_oauth_access_token(
                    '{root}/access_token'.format(root=oauth_host),
                    oauth_key,
                    oauth_secret, token_type='jwt'
                )
            except Exception:
                logger.exception('No access token provided or acquired through client_credential flow.')
                raise

            logger.info('Token retrieved: %s', access_token)

        self.api_client = EdxRestApiClient(api_url_root, jwt=self.access_token)
        self._programs_dictionary = {}
Example #17
0
    def is_verified(self, site):
        """
        Check if a user has verified his/her identity.
        Calls the LMS verification status API endpoint and returns the verification status information.
        The status information is stored in cache, if the user is verified, until the verification expires.

        Args:
            site (Site): The site object from which the LMS account API endpoint is created.

        Returns:
            True if the user is verified, false otherwise.
        """
        try:
            cache_key = 'verification_status_{username}'.format(
                username=self.username)
            cache_key = hashlib.md5(cache_key).hexdigest()
            verification_cached_response = TieredCache.get_cached_response(
                cache_key)
            if verification_cached_response.is_found:
                return verification_cached_response.value

            api = EdxRestApiClient(
                site.siteconfiguration.build_lms_url('api/user/v1/'),
                oauth_access_token=self.access_token)
            response = api.accounts(self.username).verification_status().get()

            verification = response.get('is_verified', False)
            if verification:
                cache_timeout = int(
                    (parse(response.get('expiration_datetime')) -
                     now()).total_seconds())
                TieredCache.set_all_tiers(cache_key, verification,
                                          cache_timeout)
            return verification
        except HttpNotFoundError:
            log.debug('No verification data found for [%s]', self.username)
            return False
        except (ConnectionError, SlumberBaseException, Timeout):
            msg = 'Failed to retrieve verification status details for [{username}]'.format(
                username=self.username)
            log.warning(msg)
            return False
Example #18
0
    def _query_course_structure_api(self):
        """Get course name from the Course Structure API."""
        api_client = EdxRestApiClient(
            self.site_configuration.build_lms_url('/api/course_structure/v0/'),
            jwt=self.site_configuration.access_token)
        data = api_client.courses(self.course.id).get()
        logger.debug(data)

        course_name = data.get('name')
        if course_name is None:
            message = 'Aborting migration. No name is available for {}.'.format(
                self.course.id)
            logger.error(message)
            raise Exception(message)

        # A course without entries in the LMS CourseModes table must be an honor course, meaning
        # it has no verification deadline.
        course_verification_deadline = None

        return course_name.strip(), course_verification_deadline
Example #19
0
def get_enrollment_api_client():
    """
    Retrieve a client of the Enrollment API.

    The client is authenticated using the API token from the Django settings.
    """
    session = requests.Session()
    session.headers = {"X-Edx-Api-Key": settings.EDX_API_KEY}
    return EdxRestApiClient(settings.ENTERPRISE_ENROLLMENT_API_URL,
                            append_slash=False,
                            session=session)
Example #20
0
def course_discovery_api_client(user, catalog_url):
    """
    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."))

    jwt = JwtBuilder.create_jwt_for_user(user)
    return EdxRestApiClient(catalog_url, jwt=jwt)
    def handle(self, *args, **options):
        # For each partner defined...
        partners = Partner.objects.all()

        # If a specific partner was indicated, filter down the set
        partner_code = options.get('partner_code')
        if partner_code:
            partners = partners.filter(short_code=partner_code)

        if not partners:
            raise CommandError('No partners available!')

        for partner in partners:

            access_token = options.get('access_token')
            token_type = options.get('token_type')

            if access_token and not token_type:
                raise CommandError('The token_type must be specified when passing in an access token!')

            if not access_token:
                logger.info('No access token provided. Retrieving access token using client_credential flow...')
                token_type = 'JWT'

                try:
                    access_token, __ = EdxRestApiClient.get_oauth_access_token(
                        '{root}/access_token'.format(root=partner.oidc_url_root.strip('/')),
                        partner.oidc_key,
                        partner.oidc_secret,
                        token_type=token_type
                    )
                except Exception:
                    logger.exception('No access token provided or acquired through client_credential flow.')
                    raise

            loaders = []

            if partner.organizations_api_url:
                loaders.append(OrganizationsApiDataLoader)
            if partner.courses_api_url:
                loaders.append(CoursesApiDataLoader)
            if partner.ecommerce_api_url:
                loaders.append(EcommerceApiDataLoader)
            if partner.marketing_site_api_url:
                loaders.append(DrupalApiDataLoader)
            if partner.programs_api_url:
                loaders.append(ProgramsApiDataLoader)

            if loaders:
                for loader_class in loaders:
                    try:
                        loader_class(partner, access_token, token_type).ingest()
                    except Exception:  # pylint: disable=broad-except
                        logger.exception('%s failed!', loader_class.__name__)
Example #22
0
class EnrollmentApiClient(object):
    def __init__(self):
        access_token, __ = get_access_token()
        self.client = EdxRestApiClient(ENROLLMENT_API_URL, jwt=access_token, append_slash=False)

    def get_enrollment_status(self, username, course_id):
        """
        Retrieve the enrollment status for given user in a given course.
        """
        param = '{username},{course_id}'.format(username=username, course_id=course_id)
        return self.client.enrollment(param).get()
Example #23
0
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)
Example #24
0
 def __init__(self, user):
     """
     Initialize an authenticated Enterprise service API client by using the
     provided user.
     """
     self.user = user
     jwt = create_jwt_for_user(user)
     self.client = EdxRestApiClient(
         configuration_helpers.get_value('ENTERPRISE_API_URL', settings.ENTERPRISE_API_URL),
         jwt=jwt
     )
Example #25
0
    def course_catalog_api_client(self):
        """
        Returns an API client to access the Course Catalog service.

        Returns:
            EdxRestApiClient: The client to access the Course Catalog service.
        """

        # TODO Use URL from SiteConfiguration model.
        return EdxRestApiClient(settings.COURSE_CATALOG_API_URL,
                                jwt=self.access_token)
Example #26
0
 def _hubspot_endpoint(self, hubspot_object, api_url, method, body=None, **kwargs):
     """
     This function is responsible for all the calls of hubspot.
     """
     client = EdxRestApiClient('/'.join([HUBSPOT_API_BASE_URL, api_url]))
     if method == "GET":
         return getattr(client, hubspot_object).get(**kwargs)
     if method == "POST":
         return getattr(client, hubspot_object).post(**kwargs)
     if method == "PUT":
         return getattr(client, hubspot_object).put(body, **kwargs)
Example #27
0
    def user_api_client(self):
        """
        Returns an authenticated User API client.

        Returns:
            EdxRestApiClient
        """

        return EdxRestApiClient(self.user_api_url,
                                jwt=self.access_token,
                                append_slash=False)
Example #28
0
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)
Example #29
0
    def enterprise_catalog_api_client(self):
        """
        Returns a REST API client for the provided enterprise catalog service

        Example:
            site.siteconfiguration.enterprise_catalog_api_client.enterprise-catalog.get()

        Returns:
            EdxRestApiClient: The client to access the Enterprise Catalog service.

        """
        return EdxRestApiClient(self.enterprise_catalog_api_url, jwt=self.access_token)
Example #30
0
 def get_access_token():
     """ Returns an access token and expiration date from the OAuth
     provider.
     Returns:
         (str, datetime):Tuple containing access token and expiration date.
     """
     return EdxRestApiClient.get_oauth_access_token(
         OAUTH_ACCESS_TOKEN_URL,
         OAUTH_CLIENT_ID,
         OAUTH_CLIENT_SECRET,
         token_type='jwt'
     )
Example #31
0
def get_ecommerce_client(site_code=None):
    """
    Get client for fetching data from ecommerce API

    Returns:
        EdxRestApiClient object
    """
    ecommerce_api_root = get_configuration('ECOMMERCE_API_ROOT', site_code=site_code)
    signing_key = get_configuration('JWT_SECRET_KEY', site_code=site_code)
    issuer = get_configuration('JWT_ISSUER', site_code=site_code)
    service_username = get_configuration('ECOMMERCE_SERVICE_USERNAME', site_code=site_code)
    return EdxRestApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username)
Example #32
0
    def enterprise_api_client(self):
        """
        Constructs a Slumber-based REST API client for the provided site.

        Example:
            site.siteconfiguration.enterprise_api_client.enterprise-learner(learner.username).get()

        Returns:
            EdxRestApiClient: The client to access the Enterprise service.

        """
        return EdxRestApiClient(self.enterprise_api_url, jwt=self.access_token)
Example #33
0
def create_video_pipeline_api_client(user, api_client_id, api_client_secret, api_url):
    """
    Returns an API client which can be used to make Video Pipeline API requests.

    Arguments:
        user(User): A requesting user.
        api_client_id(unicode): Video pipeline client id.
        api_client_secret(unicode): Video pipeline client secret.
        api_url(unicode): It is video pipeline's API URL.
    """
    jwt_token = create_jwt_for_user(user, secret=api_client_secret, aud=api_client_id)
    return EdxRestApiClient(api_url, jwt=jwt_token)
    def dispatch(self, request, *args, **kwargs):
        self.course_api_enabled = switch_is_active('enable_course_api')

        if self.course_api_enabled and request.user.is_authenticated:
            self.access_token = settings.COURSE_API_KEY or EdxRestApiClient.get_and_cache_jwt_oauth_access_token(
                settings.BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
                settings.BACKEND_SERVICE_EDX_OAUTH2_KEY,
                settings.BACKEND_SERVICE_EDX_OAUTH2_SECRET,
            )[0]
            self.course_api = CourseStructureApiClient(settings.COURSE_API_URL, self.access_token)

        return super(CourseAPIMixin, self).dispatch(request, *args, **kwargs)
Example #35
0
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)
Example #36
0
 def _add_dynamic_discount_to_request(self, basket):
     # TODO: Remove as a part of REVMI-124 as this is a hacky solution
     # The problem is that orders are being created after payment processing, and the discount is not
     # saved in the database, so it needs to be calculated again in order to save the correct info to the
     # order. REVMI-124 will create the order before payment processing, when we have the discount context.
     if waffle.flag_is_active(self.request, DYNAMIC_DISCOUNT_FLAG) and basket.lines.count() == 1:  # pragma: no cover  pylint: disable=line-too-long
         discount_lms_url = get_lms_url('/api/discounts/')
         lms_discount_client = EdxRestApiClient(discount_lms_url,
                                                jwt=self.request.site.siteconfiguration.access_token)
         ck = basket.lines.first().product.course_id
         user_id = basket.owner.lms_user_id
         try:
             response = lms_discount_client.user(user_id).course(ck).get()
             self.request.POST = self.request.POST.copy()
             self.request.POST['discount_jwt'] = response.get('jwt')
             logger.info(
                 """Received discount jwt from LMS with
                 url: [%s],
                 user_id: [%s],
                 course_id: [%s],
                 and basket_id: [%s]
                 returned [%s]""",
                 discount_lms_url,
                 str(user_id),
                 ck,
                 basket.id,
                 response)
         except (SlumberHttpBaseException, requests.exceptions.Timeout) as error:
             logger.warning(
                 """Failed to receive discount jwt from LMS with
                 url: [%s],
                 user_id: [%s],
                 course_id: [%s],
                 and basket_id: [%s]
                 returned [%s]""",
                 discount_lms_url,
                 str(user_id),
                 ck,
                 basket.id,
                 vars(error.response) if hasattr(error, 'response') else '')
Example #37
0
    def handle(self, *args, **options):
        access_token = options.get('access_token')
        commit = options.get('commit')

        if access_token is None:
            try:
                access_token_url = '{}/access_token'.format(
                    settings.SOCIAL_AUTH_EDX_OIDC_URL_ROOT.strip('/'))
                client_id = settings.SOCIAL_AUTH_EDX_OIDC_KEY
                client_secret = settings.SOCIAL_AUTH_EDX_OIDC_SECRET

                access_token, __ = EdxRestApiClient.get_oauth_access_token(
                    access_token_url, client_id, client_secret)
            except:  # pylint: disable=bare-except
                logger.exception(
                    'Unable to exchange client credentials grant for an access token.'
                )
                return

        self.client = EdxRestApiClient(settings.ORGANIZATIONS_API_URL_ROOT,
                                       oauth_access_token=access_token)

        logger.info('Retrieving organization data from %s.',
                    settings.ORGANIZATIONS_API_URL_ROOT)

        try:
            with transaction.atomic():
                self._get_data()

                logger.info(
                    'Retrieved %d organizations from %s, %d of which were new.',
                    self.org_count, settings.ORGANIZATIONS_API_URL_ROOT,
                    self.new_org_count)

                if not commit:
                    raise ForcedRollback(
                        'No data has been saved. To save data, pass the -c or --commit flags.'
                    )
        except ForcedRollback as e:
            logger.info(e)
Example #38
0
    def get(self, request):
        # lms/ecommerce has different user
        if 'username' in request.GET and request.user.username != request.GET.get(
                'username'):
            logout(request)
            query_dict = request.GET.dict()
            query_dict.pop('username')
            redirect_url = '{path}?{query_string}'.format(
                path=request.path, query_string=urlencode(query_dict))
            logger.info('logout user {username}'.format(
                username=request.GET.get('username')))
            return redirect(redirect_url)

        partner = get_partner_for_site(request)

        skus = [escape(sku) for sku in request.GET.getlist('sku')]
        code = request.GET.get('code', None)

        if not skus:
            return HttpResponseBadRequest(_('No SKUs provided.'))

        products = Product.objects.filter(stockrecords__partner=partner,
                                          stockrecords__partner_sku__in=skus)
        if not products:
            return HttpResponseBadRequest(
                _('Products with SKU(s) [{skus}] do not exist.').format(
                    skus=', '.join(skus)))

        try:
            lms_api = EdxRestApiClient(
                get_lms_url('/api/v1/vip/'),
                oauth_access_token=request.user.access_token,
                append_slash=False)
            # user is vip, redirect lms course about
            if lms_api.info().get().get('data', {}).get('status') is True:
                course_key = CourseKey.from_string(products[0].attr.course_key)
                return redirect(
                    get_lms_course_about_url(course_key=course_key))
        except Exception, e:
            logger.exception(e)
Example #39
0
    def is_user_already_enrolled(self, request, seat):
        """
        Check if a user is already enrolled in the course.
        Calls the LMS enrollment API endpoint and sends the course ID and username query parameters
        and returns the status of the user's enrollment in the course.

        Arguments:
            request (WSGIRequest): the request from which the LMS enrollment API endpoint is created.
            seat (Product): the seat for which the check is done if the user is enrolled in.

        Returns:
            A boolean value if the user is enrolled in the course or not.

        Raises:
            ConnectionError, SlumberBaseException and Timeout for failures in establishing a
            connection with the LMS enrollment API endpoint.
        """
        course_key = seat.attr.course_key
        try:
            api = EdxRestApiClient(
                request.site.siteconfiguration.build_lms_url(
                    '/api/enrollment/v1'),
                oauth_access_token=self.access_token,
                append_slash=False)
            status = api.enrollment(','.join([self.username,
                                              course_key])).get()
        except (ConnectionError, SlumberBaseException, Timeout) as ex:
            log.exception(
                'Failed to retrieve enrollment details for [%s] in course [%s], because of [%s]',
                self.username,
                course_key,
                ex,
            )
            raise ex

        seat_type = mode_for_seat(seat)
        if status and status.get('mode') == seat_type and status.get(
                'is_active'):
            return True
        return False
Example #40
0
 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
Example #41
0
 def __init__(self, user):
     """
     Initialize an authenticated Consent service API client by using the
     provided user.
     """
     jwt = create_jwt_for_user(user)
     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
Example #42
0
    def post(self, request):
        """Handle an incoming user returned to us by Alipay after approving payment."""
        resp = json.loads(request.POST['original_data'])['data']
        verify_ret, payment_response = self.verify_data(resp)
        if not verify_ret:
            return Response({'result': 'fail'})

        payment_id = payment_response.get('out_trade_no')
        basket = self._get_basket(payment_id)
        if not basket:
            return Response({'result': 'fail'})

        try:
            lms_api = EdxRestApiClient(
                get_lms_url('/api/user/v1/'),
                oauth_access_token=basket.owner.access_token,
                append_slash=False)
            user_lang = lms_api.preferences(basket.owner.username).get()
            translation.activate(
                user_lang.get('pref-lang', settings.LANGUAGE_CODE))
        except Exception, e:
            logger.exception(e)
Example #43
0
 def _add_dynamic_discount_to_request(self, basket):
     # TODO: Remove as a part of REVMI-124 as this is a hacky solution
     # The problem is that orders are being created after payment processing, and the discount is not
     # saved in the database, so it needs to be calculated again in order to save the correct info to the
     # order. REVMI-124 will create the order before payment processing, when we have the discount context.
     if waffle.flag_is_active(
             self.request,
             DYNAMIC_DISCOUNT_FLAG) and basket.lines.count() == 1:
         discount_lms_url = get_lms_url('/api/discounts/')
         lms_discount_client = EdxRestApiClient(
             discount_lms_url,
             jwt=self.request.site.siteconfiguration.access_token)
         ck = basket.lines.first().product.course_id
         user_id = basket.owner.lms_user_id
         try:
             response = lms_discount_client.user(user_id).course(ck).get()
             self.request.GET = self.request.GET.copy()
             self.request.GET['discount_jwt'] = response.get('jwt')
         except (SlumberHttpBaseException, Timeout) as error:
             logger.warning(
                 'Failed to get discount jwt from LMS. [%s] returned [%s]',
                 discount_lms_url, error.response)
Example #44
0
    def is_verified(self, site):
        """
        Check if a user has verified his/her identity.
        Calls the LMS verification status API endpoint and returns the verification status information.
        The status information is stored in cache, if the user is verified, until the verification expires.

        Args:
            site (Site): The site object from which the LMS account API endpoint is created.

        Returns:
            True if the user is verified, false otherwise.

        Raises:
            ConnectionError, SlumberBaseException and Timeout for failures in
            establishing a connection with the LMS verification status API endpoint.
        """
        try:
            cache_key = 'verification_status_{username}'.format(username=self.username)
            cache_key = hashlib.md5(cache_key).hexdigest()
            verification = cache.get(cache_key)
            if not verification:
                api = EdxRestApiClient(
                    site.siteconfiguration.build_lms_url('api/user/v1/'),
                    oauth_access_token=self.access_token
                )
                response = api.accounts(self.username).verification_status().get()

                verification = response.get('is_verified', False)
                if verification:
                    cache_timeout = int((parse(response.get('expiration_datetime')) - now()).total_seconds())
                    cache.set(cache_key, verification, cache_timeout)
            return verification
        except HttpNotFoundError:
            log.debug('No verification data found for [%s]', self.username)
            return False
        except (ConnectionError, SlumberBaseException, Timeout):
            msg = 'Failed to retrieve verification status details for [{username}]'.format(username=self.username)
            log.exception(msg)
            raise VerificationStatusError(msg)
Example #45
0
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)
Example #46
0
    def test_get_client_credential_access_token_success(self):
        """ Test that the get access token method handles 200 responses and returns the access token. """
        code = 200
        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(URL, code, body=body)
            self.assertEqual(
                EdxRestApiClient.get_oauth_access_token(
                    URL, "client_id", "client_secret"), expected_return)
Example #47
0
def ecommerce_api_client(user):
    """ Returns an E-Commerce API client setup with authentication for the specified user. """
    jwt_auth = configuration_helpers.get_value("JWT_AUTH", settings.JWT_AUTH)
    return EdxRestApiClient(
        configuration_helpers.get_value("ECOMMERCE_API_URL", settings.ECOMMERCE_API_URL),
        configuration_helpers.get_value("ECOMMERCE_API_SIGNING_KEY", settings.ECOMMERCE_API_SIGNING_KEY),
        user.username,
        user.profile.name if hasattr(user, 'profile') else None,
        user.email,
        tracking_context=create_tracking_context(user),
        issuer=jwt_auth['JWT_ISSUER'],
        expires_in=jwt_auth['JWT_EXPIRATION']
    )
Example #48
0
    def get_context_data(self, **kwargs):
        context = super(BasketSummaryView, self).get_context_data(**kwargs)
        lines = context.get('line_list', [])
        api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
        for line in lines:
            course_id = line.product.course_id

            # Get each course type so we can display to the user at checkout.
            try:
                line.certificate_type = get_certificate_type_display_value(line.product.attr.certificate_type)
            except ValueError:
                line.certificate_type = None

            cache_key = 'courses_api_detail_{}'.format(course_id)
            cache_hash = hashlib.md5(cache_key).hexdigest()
            try:
                course = cache.get(cache_hash)
                if not course:
                    course = api.courses(course_id).get()
                    course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
                    cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT)
                line.course = course
            except (ConnectionError, SlumberBaseException, Timeout):
                logger.exception('Failed to retrieve data from Course API for course [%s].', course_id)

            if line.has_discount:
                line.discount_percentage = line.discount_value / line.unit_price_incl_tax * Decimal(100)
            else:
                line.discount_percentage = 0

        context.update({
            'payment_processors': self.get_payment_processors(),
            'homepage_url': get_lms_url(''),
            'footer': get_lms_footer(),
            'lines': lines,
            'faq_url': get_lms_url('') + '/verified-certificate',
        })
        return context
Example #49
0
    def is_user_already_enrolled(self, request, seat):
        """
        Check if a user is already enrolled in the course.
        Calls the LMS enrollment API endpoint and sends the course ID and username query parameters
        and returns the status of the user's enrollment in the course.

        Arguments:
            request (WSGIRequest): the request from which the LMS enrollment API endpoint is created.
            seat (Product): the seat for which the check is done if the user is enrolled in.

        Returns:
            A boolean value if the user is enrolled in the course or not.

        Raises:
            ConnectionError, SlumberBaseException and Timeout for failures in establishing a
            connection with the LMS enrollment API endpoint.
        """
        course_key = seat.attr.course_key
        try:
            api = EdxRestApiClient(
                request.site.siteconfiguration.build_lms_url('/api/enrollment/v1'),
                oauth_access_token=self.access_token,
                append_slash=False
            )
            status = api.enrollment(','.join([self.username, course_key])).get()
        except (ConnectionError, SlumberBaseException, Timeout) as ex:
            log.exception(
                'Failed to retrieve enrollment details for [%s] in course [%s], Because of [%s]',
                self.username,
                course_key,
                ex,
            )
            raise ex

        seat_type = mode_for_seat(seat)
        if status and status.get('mode') == seat_type and status.get('is_active'):
            return True
        return False
Example #50
0
    def _get_basket(self, basket_id):
        if not basket_id:
            return None

        try:
            basket_id = int(basket_id)
            basket = Basket.objects.get(id=basket_id)
            basket.strategy = strategy.Default()
            # TODO: Remove as a part of REVMI-124 as this is a hacky solution
            # The problem is that orders are being created after payment processing, and the discount is not
            # saved in the database, so it needs to be calculated again in order to save the correct info to the
            # order. REVMI-124 will create the order before payment processing, when we have the discount context.
            if waffle.flag_is_active(
                    self.request,
                    DYNAMIC_DISCOUNT_FLAG) and basket.lines.count() == 1:
                discount_lms_url = get_lms_url('/api/discounts/')
                lms_discount_client = EdxRestApiClient(
                    discount_lms_url,
                    jwt=self.request.site.siteconfiguration.access_token)
                ck = basket.lines.first().product.course_id
                user_id = self.request.user.lms_user_id
                try:
                    response = lms_discount_client.user(user_id).course(
                        ck).get()
                    self.request.POST = self.request.POST.copy()
                    self.request.POST['discount_jwt'] = response.get('jwt')
                except (SlumberHttpBaseException,
                        requests.exceptions.Timeout) as error:
                    logger.warning(
                        'Failed to get discount jwt from LMS. [%s] returned [%s]',
                        discount_lms_url, error.response)
            # End TODO
            Applicator().apply(basket, basket.owner, self.request)
            logger.info('Applicator applied, basket id: [%s]', basket.id)
            return basket
        except (ValueError, ObjectDoesNotExist) as error:
            logger.warning('Could not get basket--error: [%s]', str(error))
            return None
Example #51
0
    def journal_discovery_api_client(self):
        """
        Returns an Journal API client to access the Discovery service.

        Returns:
            EdxRestApiClient: The client to access the Journal API in the Discovery service.
        """
        split_url = urlsplit(self.discovery_api_url)
        journal_discovery_url = urlunsplit([
            split_url.scheme, split_url.netloc, JOURNAL_DISCOVERY_API_PATH,
            split_url.query, split_url.fragment
        ])

        return EdxRestApiClient(journal_discovery_url, jwt=self.access_token)
Example #52
0
    def test_get_client_credential_access_token_success(self):
        """ Test that the get access token method handles 200 responses and returns the access token. """
        code = 200
        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(URL, code, body=body)
            self.assertEqual(
                EdxRestApiClient.get_oauth_access_token(URL, "client_id", "client_secret"),
                expected_return
            )
Example #53
0
    def refresh_all_ecommerce_data(cls, access_token):
        ecommerce_api_url = settings.ECOMMERCE_API_URL
        client = EdxRestApiClient(ecommerce_api_url, oauth_access_token=access_token)
        count = None
        page = 1

        logger.info('Refreshing ecommerce data from %s....', ecommerce_api_url)

        while page:
            response = client.courses().get(include_products=True, page=page, page_size=50)
            count = response['count']
            results = response['results']
            logger.info('Retrieved %d courses...', len(results))

            if response['next']:
                page += 1
            else:
                page = None

            for body in results:
                Course(body['id']).update(body)

        logger.info('Retrieved %d courses from %s.', count, ecommerce_api_url)
Example #54
0
def fulfill_order(self, order_number, site_code=None):
    """Fulfills an order.

    Arguments:
        order_number (str): Order number indicating which order to fulfill.

    Returns:
        None
    """
    ecommerce_api_root = get_configuration('ECOMMERCE_API_ROOT', site_code=site_code)
    max_fulfillment_retries = get_configuration('MAX_FULFILLMENT_RETRIES', site_code=site_code)
    signing_key = get_configuration('JWT_SECRET_KEY', site_code=site_code)
    issuer = get_configuration('JWT_ISSUER', site_code=site_code)
    service_username = get_configuration('ECOMMERCE_SERVICE_USERNAME', site_code=site_code)

    api = EdxRestApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username)
    try:
        logger.info('Requesting fulfillment of order [%s].', order_number)
        api.orders(order_number).fulfill.put()
    except exceptions.HttpClientError as exc:
        status_code = exc.response.status_code  # pylint: disable=no-member
        if status_code == 406:
            # The order is not fulfillable. Therefore, it must be complete.
            logger.info('Order [%s] has already been fulfilled. Ignoring.', order_number)
            raise Ignore()
        else:
            # Unknown client error. Let's retry to resolve it.
            logger.warning(
                'Fulfillment of order [%s] failed because of HttpClientError. Retrying',
                order_number,
                exc_info=True
            )
            _retry_order(self, exc, max_fulfillment_retries, order_number)

    except (exceptions.HttpServerError, exceptions.Timeout) as exc:
        # Fulfillment failed, retry
        _retry_order(self, exc, max_fulfillment_retries, order_number)
Example #55
0
    def test_user_agent(self):
        """Make sure our custom User-Agent is getting built correctly."""
        with mock.patch('socket.gethostbyname', return_value='test_hostname'):
            default_user_agent = user_agent()
            self.assertIn('python-requests', default_user_agent)
            self.assertIn('edx-rest-api-client/{}'.format(__version__), default_user_agent)
            self.assertIn('test_hostname', default_user_agent)

        with mock.patch('socket.gethostbyname') as mock_gethostbyname:
            mock_gethostbyname.side_effect = ValueError()
            default_user_agent = user_agent()
            self.assertIn('unknown_client_name', default_user_agent)

        with mock.patch.dict(os.environ, {'EDX_REST_API_CLIENT_NAME': "awesome_app"}):
            uagent = user_agent()
            self.assertIn('awesome_app', uagent)

        self.assertEqual(user_agent(), EdxRestApiClient.user_agent())
Example #56
0
    def handle(self, *args, **options):
        access_token = options.get('access_token')
        commit = options.get('commit')

        if access_token is None:
            try:
                access_token_url = '{}/access_token'.format(
                    settings.SOCIAL_AUTH_EDX_OIDC_URL_ROOT.strip('/')
                )
                client_id = settings.SOCIAL_AUTH_EDX_OIDC_KEY
                client_secret = settings.SOCIAL_AUTH_EDX_OIDC_SECRET

                access_token, __ = EdxRestApiClient.get_oauth_access_token(
                    access_token_url,
                    client_id,
                    client_secret
                )
            except:  # pylint: disable=bare-except
                logger.exception('Unable to exchange client credentials grant for an access token.')
                return

        self.client = EdxRestApiClient(settings.ORGANIZATIONS_API_URL_ROOT, oauth_access_token=access_token)

        logger.info('Retrieving organization data from %s.', settings.ORGANIZATIONS_API_URL_ROOT)

        try:
            with transaction.atomic():
                self._get_data()

                logger.info(
                    'Retrieved %d organizations from %s, %d of which were new.',
                    self.org_count,
                    settings.ORGANIZATIONS_API_URL_ROOT,
                    self.new_org_count
                )

                if not commit:
                    raise ForcedRollback('No data has been saved. To save data, pass the -c or --commit flags.')
        except ForcedRollback as e:
            logger.info(e)
Example #57
0
def get_course_catalog_api_client(site):
    """
    Returns an API client to access the Course Catalog service.

    Arguments:
        site (Site): The site for which to retrieve settings.

    Returns:
        EdxRestApiClient: The client to access the Course Catalog service.
    """

    access_token, __ = EdxRestApiClient.get_oauth_access_token(
        '{root}/access_token'.format(root=get_oauth2_provider_url()),
        site.siteconfiguration.oauth_settings['SOCIAL_AUTH_EDX_OIDC_KEY'],
        site.siteconfiguration.oauth_settings['SOCIAL_AUTH_EDX_OIDC_SECRET'],
        token_type='jwt'
    )
    course_catalog_client = EdxRestApiClient(
        settings.COURSE_CATALOG_API_URL,
        jwt=access_token
    )
    return course_catalog_client
Example #58
0
    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
        )
Example #59
0
    def get(self, request):
        partner = get_partner_for_site(request)

        sku = request.GET.get('sku', None)
        code = request.GET.get('code', None)

        if not sku:
            return HttpResponseBadRequest(_('No SKU provided.'))

        if code:
            voucher, __ = get_voucher_and_products_from_code(code=code)
        else:
            voucher = None

        try:
            product = StockRecord.objects.get(partner=partner, partner_sku=sku).product
        except StockRecord.DoesNotExist:
            return HttpResponseBadRequest(_('SKU [{sku}] does not exist.').format(sku=sku))

        # If the product isn't available then there's no reason to continue with the basket addition
        purchase_info = request.strategy.fetch_for_product(product)
        if not purchase_info.availability.is_available_to_buy:
            msg = _('Product [{product}] not available to buy.').format(product=product.title)
            return HttpResponseBadRequest(msg)

        # If the product is not an Enrollment Code, we check to see if the user is already
        # enrolled to prevent double-enrollment and/or accidental coupon usage
        if product.get_product_class().name != ENROLLMENT_CODE_PRODUCT_CLASS_NAME:

            course_key = product.attr.course_key

            # Submit a query to the LMS Enrollment API
            try:
                api = EdxRestApiClient(
                    get_lms_enrollment_base_api_url(),
                    oauth_access_token=request.user.access_token,
                    append_slash=False
                )
                logger.debug(
                    'Getting enrollment information for [%s] in [%s].',
                    request.user.username,
                    course_key
                )
                status = api.enrollment(','.join([request.user.username, course_key])).get()
            except (ConnectionError, SlumberBaseException, Timeout) as ex:
                logger.exception(
                    'Failed to retrieve enrollment details for [%s] in course [%s], Because of [%s]',
                    request.user.username,
                    course_key,
                    ex,
                )
                msg = _('An error occurred while retrieving enrollment details. Please try again.')
                return HttpResponseBadRequest(msg)

            # Enrollment API response received, now perform the actual enrollment check
            username = request.user.username
            seat_type = mode_for_seat(product)
            if status and status.get('mode') == seat_type and status.get('is_active'):
                logger.warning(
                    'User [%s] attempted to repurchase the [%s] seat of course [%s]',
                    username,
                    seat_type,
                    course_key
                )
                msg = _('You are already enrolled in {course}.').format(course=product.course.name)
                return HttpResponseBadRequest(msg)

        # At this point we're either adding an Enrollment Code product to the basket,
        # or the user is adding a Seat product for which they are not already enrolled
        prepare_basket(request, product, voucher)
        return HttpResponseRedirect(reverse('basket:summary'), status=303)
Example #60
0
    def get_context_data(self, **kwargs):
        context = super(BasketSummaryView, self).get_context_data(**kwargs)
        formset = context.get('formset', [])
        lines = context.get('line_list', [])
        lines_data = []
        api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
        is_verification_required = False
        for line in lines:
            course_key = CourseKey.from_string(line.product.attr.course_key)
            cache_key = 'courses_api_detail_{}'.format(course_key)
            cache_hash = hashlib.md5(cache_key).hexdigest()
            course_name = None
            image_url = None
            short_description = None
            try:
                course = cache.get(cache_hash)
                if not course:
                    course = api.courses(course_key).get()
                    cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT)
                image_url = get_lms_url(course['media']['course_image']['uri'])
                short_description = course['short_description']
                course_name = course['name']
            except (ConnectionError, SlumberBaseException, Timeout):
                logger.exception('Failed to retrieve data from Course API for course [%s].', course_key)

            if line.has_discount:
                benefit = self.request.basket.applied_offers().values()[0].benefit
                benefit_value = format_benefit_value(benefit)
            else:
                benefit_value = None

            lines_data.append({
                'seat_type': self._determine_seat_type(line.product),
                'course_name': course_name,
                'course_key': course_key,
                'image_url': image_url,
                'course_short_description': short_description,
                'benefit_value': benefit_value,
                'enrollment_code': line.product.get_product_class().name == ENROLLMENT_CODE_PRODUCT_CLASS_NAME,
                'line': line,
            })

            context.update({
                'analytics_data': prepare_analytics_data(
                    self.request.user,
                    self.request.site.siteconfiguration.segment_key,
                    unicode(course_key)
                ),
            })

            # Check product attributes to determine if ID verification is required for this basket
            try:
                is_verification_required = is_verification_required or line.product.attr.id_verification_required
            except AttributeError:
                pass

        context.update({
            'free_basket': context['order_total'].incl_tax == 0,
            'payment_processors': self.request.site.siteconfiguration.get_payment_processors(),
            'homepage_url': get_lms_url(''),
            'formset_lines_data': zip(formset, lines_data),
            'is_verification_required': is_verification_required,
        })
        return context