Country Access: block enrollment
Block users from enrolling in a course if the user is blocked by country access rules. 1) Enrollment via the login/registration page. 2) Enrollment from the marketing iframe (via student.views.change_enrollment) 3) Enrollment using 100% redeem codes. 4) Enrollment via upgrade. This does NOT cover enrollment through third party authentication, which is sufficiently complex to deserve its own commit.
This commit is contained in:
@@ -11,6 +11,7 @@ from xmodule.modulestore.tests.django_utils import (
|
||||
)
|
||||
|
||||
from util.testing import UrlResetMixin
|
||||
from embargo.test_utils import restrict_course
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
@@ -274,7 +275,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
# Create a verified mode
|
||||
url = reverse('create_mode', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url, parameters)
|
||||
self.client.get(url, parameters)
|
||||
|
||||
honor_mode = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None)
|
||||
verified_mode = Mode(u'verified', u'Verified Certificate', 10, '10,20', 'usd', None, None)
|
||||
@@ -282,3 +283,34 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
course_modes = CourseMode.modes_for_course(self.course.id)
|
||||
|
||||
self.assertEquals(course_modes, expected_modes)
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test embargo restrictions on the track selection page. """
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def setUp(self):
|
||||
super(TrackSelectionEmbargoTest, self).setUp('embargo')
|
||||
|
||||
# Create a course and course modes
|
||||
self.course = CourseFactory.create()
|
||||
CourseModeFactory(mode_slug='honor', course_id=self.course.id)
|
||||
CourseModeFactory(mode_slug='verified', course_id=self.course.id, min_price=10)
|
||||
|
||||
# Create a user and log in
|
||||
self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx")
|
||||
self.client.login(username=self.user.username, password="edx")
|
||||
|
||||
# Construct the URL for the track selection page
|
||||
self.url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_restrict(self):
|
||||
with restrict_course(self.course.id) as redirect_url:
|
||||
response = self.client.get(self.url)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
def test_embargo_allow(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -3,6 +3,8 @@ Views for the course_mode module
|
||||
"""
|
||||
|
||||
import decimal
|
||||
from ipware.ip import get_ip
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
@@ -22,6 +24,8 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from util.db import commit_on_success_with_read_committed
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from embargo import api as embargo_api
|
||||
|
||||
|
||||
class ChooseModeView(View):
|
||||
"""View used when the user is asked to pick a mode.
|
||||
@@ -52,6 +56,17 @@ class ChooseModeView(View):
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
|
||||
# Check whether the user has access to this course
|
||||
# based on country access rules.
|
||||
embargo_redirect = embargo_api.redirect_if_blocked(
|
||||
course_key,
|
||||
user=request.user,
|
||||
ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if embargo_redirect:
|
||||
return redirect(embargo_redirect)
|
||||
|
||||
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key)
|
||||
modes = CourseMode.modes_for_course_dict(course_key)
|
||||
|
||||
|
||||
@@ -6,19 +6,18 @@ import json
|
||||
import unittest
|
||||
|
||||
from mock import patch
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase, mixed_store_config
|
||||
)
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from util.testing import UrlResetMixin
|
||||
from enrollment import api
|
||||
from enrollment.errors import CourseEnrollmentError
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
from student.models import CourseEnrollment
|
||||
from embargo.test_utils import restrict_course
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -245,3 +244,66 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase):
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("No course ", resp.content)
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test that enrollment is blocked from embargoed countries. """
|
||||
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def setUp(self):
|
||||
""" Create a course and user, then log in. """
|
||||
super(EnrollmentEmbargoTest, self).setUp('embargo')
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_change_enrollment_restrict(self):
|
||||
url = reverse('courseenrollments')
|
||||
data = json.dumps({
|
||||
'course_details': {
|
||||
'course_id': unicode(self.course.id)
|
||||
},
|
||||
'user': self.user.username
|
||||
})
|
||||
|
||||
# Attempt to enroll from a country embargoed for this course
|
||||
with restrict_course(self.course.id) as redirect_url:
|
||||
response = self.client.post(url, data, content_type='application/json')
|
||||
|
||||
# Expect an error response
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Expect that the redirect URL is included in the response
|
||||
resp_data = json.loads(response.content)
|
||||
self.assertEqual(resp_data['user_message_url'], redirect_url)
|
||||
|
||||
# Verify that we were not enrolled
|
||||
self.assertEqual(self._get_enrollments(), [])
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_change_enrollment_allow(self):
|
||||
url = reverse('courseenrollments')
|
||||
data = json.dumps({
|
||||
'course_details': {
|
||||
'course_id': unicode(self.course.id)
|
||||
},
|
||||
'user': self.user.username
|
||||
})
|
||||
|
||||
response = self.client.post(url, data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Verify that we were enrolled
|
||||
self.assertEqual(len(self._get_enrollments()), 1)
|
||||
|
||||
def _get_enrollments(self):
|
||||
"""Retrieve the enrollment list for the current user. """
|
||||
url = reverse('courseenrollments')
|
||||
resp = self.client.get(url)
|
||||
return json.loads(resp.content)
|
||||
|
||||
@@ -3,15 +3,19 @@ The Enrollment API Views should be simple, lean HTTP endpoints for API access. T
|
||||
consist primarily of authentication, request validation, and serialization.
|
||||
|
||||
"""
|
||||
from opaque_keys import InvalidKeyError
|
||||
from ipware.ip import get_ip
|
||||
from django.conf import settings
|
||||
from rest_framework import status
|
||||
from rest_framework.authentication import OAuth2Authentication
|
||||
from rest_framework import permissions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
from enrollment import api
|
||||
from enrollment.errors import CourseNotFoundError, CourseEnrollmentError, CourseModeNotFoundError
|
||||
from embargo import api as embargo_api
|
||||
from util.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from util.disable_rate_limit import can_disable_rate_limit
|
||||
|
||||
@@ -278,7 +282,36 @@ class EnrollmentListView(APIView):
|
||||
course_id = request.DATA['course_details']['course_id']
|
||||
|
||||
try:
|
||||
return Response(api.add_enrollment(user, course_id))
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
except InvalidKeyError:
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
data={
|
||||
"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
|
||||
}
|
||||
)
|
||||
|
||||
# Check whether any country access rules block the user from enrollment
|
||||
# We do this at the view level (rather than the Python API level)
|
||||
# because this check requires information about the HTTP request.
|
||||
redirect_url = embargo_api.redirect_if_blocked(
|
||||
course_id, user=request.user,
|
||||
ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if redirect_url:
|
||||
return Response(
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
data={
|
||||
"message": (
|
||||
u"Users from this location cannot access the course '{course_id}'."
|
||||
).format(course_id=course_id),
|
||||
"user_message_url": redirect_url
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
return Response(api.add_enrollment(user, unicode(course_id)))
|
||||
except CourseModeNotFoundError as error:
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -305,10 +338,3 @@ class EnrollmentListView(APIView):
|
||||
).format(user=user, course_id=course_id)
|
||||
}
|
||||
)
|
||||
except InvalidKeyError:
|
||||
return Response(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
data={
|
||||
"message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,18 +5,19 @@ import ddt
|
||||
import unittest
|
||||
from mock import patch
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from util.testing import UrlResetMixin
|
||||
from embargo.test_utils import restrict_course
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EnrollmentTest(ModuleStoreTestCase):
|
||||
class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Test student enrollment, especially with different course modes.
|
||||
"""
|
||||
@@ -25,9 +26,10 @@ class EnrollmentTest(ModuleStoreTestCase):
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def setUp(self):
|
||||
""" Create a course and user, then log in. """
|
||||
super(EnrollmentTest, self).setUp()
|
||||
super(EnrollmentTest, self).setUp('embargo')
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
@@ -132,6 +134,29 @@ class EnrollmentTest(ModuleStoreTestCase):
|
||||
else:
|
||||
self.assertFalse(mock_update_email_opt_in.called)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_restrict(self):
|
||||
# When accessing the course from an embargoed country,
|
||||
# we should be blocked.
|
||||
with restrict_course(self.course.id) as redirect_url:
|
||||
response = self._change_enrollment('enroll')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, redirect_url)
|
||||
|
||||
# Verify that we weren't enrolled
|
||||
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
self.assertFalse(is_enrolled)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_allow(self):
|
||||
response = self._change_enrollment('enroll')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, '')
|
||||
|
||||
# Verify that we were enrolled
|
||||
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
self.assertTrue(is_enrolled)
|
||||
|
||||
def test_user_not_authenticated(self):
|
||||
# Log out, so we're no longer authenticated
|
||||
self.client.logout()
|
||||
|
||||
@@ -9,6 +9,7 @@ import time
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from pytz import UTC
|
||||
from ipware.ip import get_ip
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout, authenticate, login
|
||||
@@ -113,6 +114,8 @@ from xmodule.error_module import ErrorDescriptor
|
||||
from shoppingcart.models import DonationConfiguration, CourseRegistrationCode
|
||||
from openedx.core.djangoapps.user_api.api import profile as profile_api
|
||||
|
||||
from embargo import api as embargo_api
|
||||
|
||||
import analytics
|
||||
from eventtracking import tracker
|
||||
|
||||
@@ -876,6 +879,17 @@ def change_enrollment(request, check_access=True):
|
||||
|
||||
available_modes = CourseMode.modes_for_course_dict(course_id)
|
||||
|
||||
# Check whether the user is blocked from enrolling in this course
|
||||
# This can occur if the user's IP is on a global blacklist
|
||||
# or if the user is enrolling in a country in which the course
|
||||
# is not available.
|
||||
redirect_url = embargo_api.redirect_if_blocked(
|
||||
course_id, user=user, ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if redirect_url:
|
||||
return HttpResponse(redirect_url)
|
||||
|
||||
# Check that auto enrollment is allowed for this course
|
||||
# (= the course is NOT behind a paywall)
|
||||
if CourseMode.can_auto_enroll(course_id):
|
||||
|
||||
@@ -24,12 +24,11 @@ from datetime import datetime, timedelta
|
||||
from mock import patch, Mock
|
||||
import ddt
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase, mixed_store_config
|
||||
)
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.roles import CourseSalesAdminRole
|
||||
from util.date_utils import get_default_time_display
|
||||
from util.testing import UrlResetMixin
|
||||
|
||||
from shoppingcart.views import _can_download_report, _get_date_from_str
|
||||
from shoppingcart.models import (
|
||||
@@ -42,6 +41,7 @@ from courseware.tests.factories import InstructorFactory
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from embargo.test_utils import restrict_course
|
||||
from shoppingcart.processors import render_purchase_form_html
|
||||
from shoppingcart.admin import SoftDeleteCouponAdmin
|
||||
from shoppingcart.views import initialize_report
|
||||
@@ -1578,6 +1578,51 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
|
||||
self.assertIn(self.course.display_name, response.content)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test blocking redeem code redemption based on country access rules. """
|
||||
|
||||
USERNAME = 'bob'
|
||||
PASSWORD = 'test'
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def setUp(self):
|
||||
super(RedeemCodeEmbargoTests, self).setUp('embargo')
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.user.username, password=self.PASSWORD)
|
||||
self.assertTrue(result, msg="Could not log in")
|
||||
|
||||
@ddt.data('get', 'post')
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_registration_code_redemption_embargo(self, method):
|
||||
# Create a valid registration code
|
||||
reg_code = CourseRegistrationCode.objects.create(
|
||||
code="abcd1234",
|
||||
course_id=self.course.id,
|
||||
created_by=self.user
|
||||
)
|
||||
|
||||
# Try to redeem the code from a restricted country
|
||||
with restrict_course(self.course.id) as redirect_url:
|
||||
url = reverse(
|
||||
'register_code_redemption',
|
||||
kwargs={'registration_code': 'abcd1234'}
|
||||
)
|
||||
response = getattr(self.client, method)(url)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
# The registration code should NOT be redeemed
|
||||
is_redeemed = RegistrationCodeRedemption.objects.filter(
|
||||
registration_code=reg_code
|
||||
).exists()
|
||||
self.assertFalse(is_redeemed)
|
||||
|
||||
# The user should NOT be enrolled
|
||||
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
self.assertFalse(is_enrolled)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DonationViewTest(ModuleStoreTestCase):
|
||||
"""Tests for making a donation.
|
||||
|
||||
@@ -2,9 +2,11 @@ import logging
|
||||
import datetime
|
||||
import decimal
|
||||
import pytz
|
||||
from ipware.ip import get_ip
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.shortcuts import redirect
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseRedirect, HttpResponseNotFound,
|
||||
HttpResponseBadRequest, HttpResponseForbidden, Http404
|
||||
@@ -28,6 +30,7 @@ from config_models.decorators import require_config
|
||||
from shoppingcart.reports import RefundReport, ItemizedPurchaseReport, UniversityRevenueShareReport, CertificateStatusReport
|
||||
from student.models import CourseEnrollment, EnrollmentClosedError, CourseFullError, \
|
||||
AlreadyEnrolledError
|
||||
from embargo import api as embargo_api
|
||||
from .exceptions import (
|
||||
ItemAlreadyInCartException, AlreadyEnrolledInCourseException,
|
||||
CourseDoesNotExistException, ReportTypeDoesNotExistException,
|
||||
@@ -50,6 +53,7 @@ import json
|
||||
from xmodule_django.models import CourseKeyField
|
||||
from .decorators import enforce_shopping_cart_enabled
|
||||
|
||||
|
||||
log = logging.getLogger("shoppingcart")
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
|
||||
@@ -352,6 +356,15 @@ def register_code_redemption(request, registration_code):
|
||||
reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code,
|
||||
request, limiter)
|
||||
course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0)
|
||||
|
||||
# Restrict the user from enrolling based on country access rules
|
||||
embargo_redirect = embargo_api.redirect_if_blocked(
|
||||
course.id, user=request.user, ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if embargo_redirect is not None:
|
||||
return redirect(embargo_redirect)
|
||||
|
||||
context = {
|
||||
'reg_code_already_redeemed': reg_code_already_redeemed,
|
||||
'reg_code_is_valid': reg_code_is_valid,
|
||||
@@ -365,6 +378,15 @@ def register_code_redemption(request, registration_code):
|
||||
reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code,
|
||||
request, limiter)
|
||||
course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0)
|
||||
|
||||
# Restrict the user from enrolling based on country access rules
|
||||
embargo_redirect = embargo_api.redirect_if_blocked(
|
||||
course.id, user=request.user, ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if embargo_redirect is not None:
|
||||
return redirect(embargo_redirect)
|
||||
|
||||
context = {
|
||||
'reg_code': registration_code,
|
||||
'site_name': site_name,
|
||||
|
||||
@@ -32,6 +32,8 @@ from student.models import CourseEnrollment
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from course_modes.models import CourseMode
|
||||
from shoppingcart.models import Order, CertificateItem
|
||||
from embargo.test_utils import restrict_course
|
||||
from util.testing import UrlResetMixin
|
||||
from verify_student.views import render_to_response, PayAndVerifyView
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
from reverification.tests.factories import MidcourseReverificationWindowFactory
|
||||
@@ -60,7 +62,7 @@ class StartView(TestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestPayAndVerifyView(ModuleStoreTestCase):
|
||||
class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the payment and verification flow views.
|
||||
"""
|
||||
@@ -72,8 +74,9 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
|
||||
YESTERDAY = NOW - timedelta(days=1)
|
||||
TOMORROW = NOW + timedelta(days=1)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def setUp(self):
|
||||
super(TestPayAndVerifyView, self).setUp()
|
||||
super(TestPayAndVerifyView, self).setUp('embargo')
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.assertTrue(result, msg="Could not log in")
|
||||
@@ -622,6 +625,20 @@ class TestPayAndVerifyView(ModuleStoreTestCase):
|
||||
self.assertContains(response, "verification deadline")
|
||||
self.assertContains(response, "Jan 02, 1999 at 00:00 UTC")
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_restrict(self):
|
||||
course = self._create_course("verified")
|
||||
with restrict_course(course.id) as redirect_url:
|
||||
# Simulate that we're embargoed from accessing this
|
||||
# course based on our IP address.
|
||||
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
|
||||
def test_embargo_allow(self):
|
||||
course = self._create_course("verified")
|
||||
self._get_page('verify_student_start_flow', course.id)
|
||||
|
||||
def _create_course(self, *course_modes, **kwargs):
|
||||
"""Create a new course with the specified course modes. """
|
||||
course = CourseFactory.create()
|
||||
|
||||
@@ -8,6 +8,7 @@ import decimal
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
from pytz import UTC
|
||||
from ipware.ip import get_ip
|
||||
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
|
||||
@@ -46,6 +47,8 @@ from .exceptions import WindowExpiredException
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from microsite_configuration import microsite
|
||||
|
||||
from embargo import api as embargo_api
|
||||
|
||||
from util.json_request import JsonResponse
|
||||
from util.date_utils import get_default_time_display
|
||||
|
||||
@@ -256,6 +259,17 @@ class PayAndVerifyView(View):
|
||||
log.warn(u"No course specified for verification flow request.")
|
||||
raise Http404
|
||||
|
||||
# Check whether the user has access to this course
|
||||
# based on country access rules.
|
||||
redirect_url = embargo_api.redirect_if_blocked(
|
||||
course_key,
|
||||
user=request.user,
|
||||
ip_address=get_ip(request),
|
||||
url=request.path
|
||||
)
|
||||
if redirect_url:
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Check that the course has an unexpired verified mode
|
||||
course_mode, expired_course_mode = self._get_verified_modes_for_course(course_key)
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
|
||||
|
||||
var COURSE_KEY = 'edX/DemoX/Fall',
|
||||
ENROLL_URL = '/api/enrollment/v1/enrollment',
|
||||
FORWARD_URL = '/course_modes/choose/edX/DemoX/Fall/';
|
||||
FORWARD_URL = '/course_modes/choose/edX/DemoX/Fall/',
|
||||
EMBARGO_MSG_URL = '/embargo/blocked-message/enrollment/default/';
|
||||
|
||||
beforeEach(function() {
|
||||
// Mock the redirect call
|
||||
@@ -49,6 +50,27 @@ define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
|
||||
expect(EnrollmentInterface.redirect).toHaveBeenCalledWith( FORWARD_URL );
|
||||
});
|
||||
|
||||
it('redirects the user if blocked by an embargo', function() {
|
||||
// Spy on Ajax requests
|
||||
var requests = AjaxHelpers.requests( this );
|
||||
|
||||
// Attempt to enroll the user
|
||||
EnrollmentInterface.enroll( COURSE_KEY );
|
||||
|
||||
// Simulate an error response (403) from the server
|
||||
// with a "user_message_url" parameter for the redirect.
|
||||
// This will redirect the user to a page with messaging
|
||||
// explaining why he/she can't enroll.
|
||||
AjaxHelpers.respondWithError(
|
||||
requests, 403,
|
||||
{ 'user_message_url': EMBARGO_MSG_URL }
|
||||
);
|
||||
|
||||
// Verify that the user was redirected
|
||||
expect(EnrollmentInterface.redirect).toHaveBeenCalledWith( EMBARGO_MSG_URL );
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -36,7 +36,26 @@ var edx = edx || {};
|
||||
data: data,
|
||||
headers: this.headers,
|
||||
context: this
|
||||
}).always(function() {
|
||||
})
|
||||
.fail(function( jqXHR ) {
|
||||
var responseData = JSON.parse(jqXHR.responseText);
|
||||
if ( jqXHR.status === 403 && responseData.user_message_url ) {
|
||||
// Check if we've been blocked from the course
|
||||
// because of country access rules.
|
||||
// If so, redirect to a page explaining to the user
|
||||
// why they were blocked.
|
||||
this.redirect( responseData.user_message_url );
|
||||
}
|
||||
else {
|
||||
// Otherwise, go to the track selection page as usual.
|
||||
// This can occur, for example, when a course does not
|
||||
// have a free enrollment mode, so we can't auto-enroll.
|
||||
this.redirect( this.trackSelectionUrl( courseKey ) );
|
||||
}
|
||||
})
|
||||
.done(function() {
|
||||
// If we successfully enrolled, go to the track selection
|
||||
// page to allow the user to choose a paid enrollment mode.
|
||||
this.redirect( this.trackSelectionUrl( courseKey ) );
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user