Commit ae8ddc8a by Will Daly

Deprecate old embargo implementation.

The new "country access" implementation replaces the old
implementation.  Middleware and tests have been updated
accordingly, but deprecated models are preserved
for backwards compatibility.
parent 94daf078
...@@ -94,12 +94,10 @@ FEATURES = { ...@@ -94,12 +94,10 @@ FEATURES = {
# Hide any Personally Identifiable Information from application logs # Hide any Personally Identifiable Information from application logs
'SQUELCH_PII_IN_LOGS': False, 'SQUELCH_PII_IN_LOGS': False,
# Toggles the embargo functionality, which enable embargoing for particular courses # Toggles the embargo functionality, which blocks users
# based on their location.
'EMBARGO': False, 'EMBARGO': False,
# Toggles the embargo site functionality, which enable embargoing for the whole site
'SITE_EMBARGOED': False,
# Turn on/off Microsites feature # Turn on/off Microsites feature
'USE_MICROSITES': False, 'USE_MICROSITES': False,
......
...@@ -60,7 +60,6 @@ urlpatterns += patterns( ...@@ -60,7 +60,6 @@ urlpatterns += patterns(
# ajax view that actually does the work # ajax view that actually does the work
url(r'^login_post$', 'student.views.login_user', name='login_post'), url(r'^login_post$', 'student.views.login_user', name='login_post'),
url(r'^logout$', 'student.views.logout_user', name='logout'), url(r'^logout$', 'student.views.logout_user', name='logout'),
url(r'^embargo$', 'student.views.embargo', name="embargo"),
) )
# restful api # restful api
......
...@@ -289,7 +289,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -289,7 +289,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase): class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
"""Test embargo restrictions on the track selection page. """ """Test embargo restrictions on the track selection page. """
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(TrackSelectionEmbargoTest, self).setUp('embargo') super(TrackSelectionEmbargoTest, self).setUp('embargo')
...@@ -305,7 +305,7 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -305,7 +305,7 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
# Construct the URL for the track selection page # Construct the URL for the track selection page
self.url = reverse('course_modes_choose', args=[unicode(self.course.id)]) self.url = reverse('course_modes_choose', args=[unicode(self.course.id)])
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self): def test_embargo_restrict(self):
with restrict_course(self.course.id) as redirect_url: with restrict_course(self.course.id) as redirect_url:
response = self.client.get(self.url) response = self.client.get(self.url)
......
...@@ -5,51 +5,8 @@ from django.contrib import admin ...@@ -5,51 +5,8 @@ from django.contrib import admin
import textwrap import textwrap
from config_models.admin import ConfigurationModelAdmin from config_models.admin import ConfigurationModelAdmin
from embargo.models import ( from embargo.models import IPFilter, CountryAccessRule, RestrictedCourse
EmbargoedCourse, EmbargoedState, IPFilter, from embargo.forms import IPFilterForm, RestrictedCourseForm
CountryAccessRule, RestrictedCourse
)
from embargo.forms import (
EmbargoedCourseForm, EmbargoedStateForm, IPFilterForm,
RestrictedCourseForm
)
class EmbargoedCourseAdmin(admin.ModelAdmin):
"""Admin for embargoed course ids"""
form = EmbargoedCourseForm
fieldsets = (
(None, {
'fields': ('course_id', 'embargoed'),
'description': textwrap.dedent("""\
Enter a course id in the following box.
Do not enter leading or trailing slashes. There is no need to surround the
course ID with quotes.
Validation will be performed on the course name, and if it is invalid, an
error message will display.
To enable embargos against this course (restrict course access from embargoed
states), check the "Embargoed" box, then click "Save".
""")
}),
)
class EmbargoedStateAdmin(ConfigurationModelAdmin):
"""Admin for embargoed countries"""
form = EmbargoedStateForm
fieldsets = (
(None, {
'fields': ('enabled', 'embargoed_countries',),
'description': textwrap.dedent("""Enter the two-letter ISO-3166-1 Alpha-2
code of the country or countries to embargo in the following box. For help,
see <a href="http://en.wikipedia.org/wiki/ISO_3166-1#Officially_assigned_code_elements">
this list of ISO-3166-1 country codes</a>.
Enter the embargoed country codes separated by a comma. Do not surround with quotes.
""")
}),
)
class IPFilterAdmin(ConfigurationModelAdmin): class IPFilterAdmin(ConfigurationModelAdmin):
...@@ -81,7 +38,5 @@ class RestrictedCourseAdmin(admin.ModelAdmin): ...@@ -81,7 +38,5 @@ class RestrictedCourseAdmin(admin.ModelAdmin):
form = RestrictedCourseForm form = RestrictedCourseForm
admin.site.register(EmbargoedCourse, EmbargoedCourseAdmin)
admin.site.register(EmbargoedState, EmbargoedStateAdmin)
admin.site.register(IPFilter, IPFilterAdmin) admin.site.register(IPFilter, IPFilterAdmin)
admin.site.register(RestrictedCourse, RestrictedCourseAdmin) admin.site.register(RestrictedCourse, RestrictedCourseAdmin)
...@@ -27,7 +27,7 @@ def redirect_if_blocked(course_key, access_point='enrollment', **kwargs): ...@@ -27,7 +27,7 @@ def redirect_if_blocked(course_key, access_point='enrollment', **kwargs):
Same as `check_course_access` and `message_url_path` Same as `check_course_access` and `message_url_path`
""" """
if settings.FEATURES.get('ENABLE_COUNTRY_ACCESS'): if settings.FEATURES.get('EMBARGO'):
is_blocked = not check_course_access(course_key, **kwargs) is_blocked = not check_course_access(course_key, **kwargs)
if is_blocked: if is_blocked:
return message_url_path(course_key, access_point) return message_url_path(course_key, access_point)
...@@ -52,7 +52,7 @@ def check_course_access(course_key, user=None, ip_address=None, url=None): ...@@ -52,7 +52,7 @@ def check_course_access(course_key, user=None, ip_address=None, url=None):
""" """
# No-op if the country access feature is not enabled # No-op if the country access feature is not enabled
if not settings.FEATURES.get('ENABLE_COUNTRY_ACCESS'): if not settings.FEATURES.get('EMBARGO'):
return True return True
# First, check whether there are any restrictions on the course. # First, check whether there are any restrictions on the course.
......
...@@ -11,15 +11,11 @@ from xmodule.modulestore.django import modulestore ...@@ -11,15 +11,11 @@ from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from embargo.models import ( from embargo.models import IPFilter, RestrictedCourse
EmbargoedCourse, EmbargoedState, IPFilter,
RestrictedCourse
)
from embargo.fixtures.country_codes import COUNTRY_CODES
class CourseKeyValidationForm(forms.ModelForm): class RestrictedCourseForm(forms.ModelForm):
"""Base class for validating the "course_key" (or "course_id") field. """Validate course keys for the RestrictedCourse model.
The default behavior in Django admin is to: The default behavior in Django admin is to:
* Save course keys for courses that do not exist. * Save course keys for courses that do not exist.
...@@ -29,16 +25,10 @@ class CourseKeyValidationForm(forms.ModelForm): ...@@ -29,16 +25,10 @@ class CourseKeyValidationForm(forms.ModelForm):
error message instead. error message instead.
""" """
class Meta: # pylint: disable=missing-docstring
def clean_course_id(self): model = RestrictedCourse
"""Clean the 'course_id' field in the form. """
return self._clean_course_key("course_id")
def clean_course_key(self): def clean_course_key(self):
"""Clean the 'course_key' field in the form. """
return self._clean_course_key("course_key")
def _clean_course_key(self, field_name):
"""Validate the course key. """Validate the course key.
Checks that the key format is valid and that Checks that the key format is valid and that
...@@ -51,7 +41,7 @@ class CourseKeyValidationForm(forms.ModelForm): ...@@ -51,7 +41,7 @@ class CourseKeyValidationForm(forms.ModelForm):
CourseKey CourseKey
""" """
cleaned_id = self.cleaned_data[field_name] cleaned_id = self.cleaned_data['course_key']
error_msg = _('COURSE NOT FOUND. Please check that the course ID is valid.') error_msg = _('COURSE NOT FOUND. Please check that the course ID is valid.')
try: try:
...@@ -65,51 +55,6 @@ class CourseKeyValidationForm(forms.ModelForm): ...@@ -65,51 +55,6 @@ class CourseKeyValidationForm(forms.ModelForm):
return course_key return course_key
class EmbargoedCourseForm(CourseKeyValidationForm):
"""Validate course keys for the EmbargoedCourse model. """
class Meta: # pylint: disable=missing-docstring
model = EmbargoedCourse
class RestrictedCourseForm(CourseKeyValidationForm):
"""Validate course keys for the RestirctedCourse model. """
class Meta: # pylint: disable=missing-docstring
model = RestrictedCourse
class EmbargoedStateForm(forms.ModelForm): # pylint: disable=incomplete-protocol
"""Form validating entry of states to embargo"""
class Meta: # pylint: disable=missing-docstring
model = EmbargoedState
def _is_valid_code(self, code):
"""Whether or not code is a valid country code"""
return code in COUNTRY_CODES
def clean_embargoed_countries(self):
"""Validate the country list"""
embargoed_countries = self.cleaned_data["embargoed_countries"]
if not embargoed_countries:
return ''
error_countries = []
for country in embargoed_countries.split(','):
country = country.strip().upper()
if not self._is_valid_code(country):
error_countries.append(country)
if error_countries:
msg = 'COULD NOT PARSE COUNTRY CODE(S) FOR: {0}'.format(error_countries)
msg += ' Please check the list of country codes and verify your entries.'
raise forms.ValidationError(msg)
return embargoed_countries
class IPFilterForm(forms.ModelForm): # pylint: disable=incomplete-protocol class IPFilterForm(forms.ModelForm): # pylint: disable=incomplete-protocol
"""Form validating entry of IP addresses""" """Form validating entry of IP addresses"""
......
...@@ -37,6 +37,8 @@ log = logging.getLogger(__name__) ...@@ -37,6 +37,8 @@ log = logging.getLogger(__name__)
class EmbargoedCourse(models.Model): class EmbargoedCourse(models.Model):
""" """
Enable course embargo on a course-by-course basis. Enable course embargo on a course-by-course basis.
Deprecated by `RestrictedCourse`
""" """
objects = NoneToEmptyManager() objects = NoneToEmptyManager()
...@@ -70,6 +72,8 @@ class EmbargoedCourse(models.Model): ...@@ -70,6 +72,8 @@ class EmbargoedCourse(models.Model):
class EmbargoedState(ConfigurationModel): class EmbargoedState(ConfigurationModel):
""" """
Register countries to be embargoed. Register countries to be embargoed.
Deprecated by `Country`.
""" """
# The countries to embargo # The countries to embargo
embargoed_countries = models.TextField( embargoed_countries = models.TextField(
......
...@@ -36,7 +36,7 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, incl ...@@ -36,7 +36,7 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, incl
@ddt.ddt @ddt.ddt
@override_settings(MODULESTORE=MODULESTORE_CONFIG) @override_settings(MODULESTORE=MODULESTORE_CONFIG)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
class EmbargoCheckAccessApiTests(ModuleStoreTestCase): class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
"""Test the embargo API calls to determine whether a user has access. """ """Test the embargo API calls to determine whether a user has access. """
...@@ -137,7 +137,7 @@ class EmbargoCheckAccessApiTests(ModuleStoreTestCase): ...@@ -137,7 +137,7 @@ class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='FE80::0202:B3FF:FE1E:8329') result = embargo_api.check_course_access(self.course.id, user=self.user, ip_address='FE80::0202:B3FF:FE1E:8329')
self.assertTrue(result) self.assertTrue(result)
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_profile_country_db_null(self): def test_profile_country_db_null(self):
# Django country fields treat NULL values inconsistently. # Django country fields treat NULL values inconsistently.
# When saving a profile with country set to None, Django saves an empty string to the database. # When saving a profile with country set to None, Django saves an empty string to the database.
...@@ -183,7 +183,7 @@ class EmbargoCheckAccessApiTests(ModuleStoreTestCase): ...@@ -183,7 +183,7 @@ class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
class EmbargoMessageUrlApiTests(UrlResetMixin, ModuleStoreTestCase): class EmbargoMessageUrlApiTests(UrlResetMixin, ModuleStoreTestCase):
"""Test the embargo API calls for retrieving the blocking message URLs. """ """Test the embargo API calls for retrieving the blocking message URLs. """
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(EmbargoMessageUrlApiTests, self).setUp('embargo') super(EmbargoMessageUrlApiTests, self).setUp('embargo')
self.course = CourseFactory.create() self.course = CourseFactory.create()
......
...@@ -4,139 +4,50 @@ Unit tests for embargo app admin forms. ...@@ -4,139 +4,50 @@ Unit tests for embargo app admin forms.
""" """
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings
from opaque_keys.edx.locator import CourseLocator
# Explicitly import the cache from ConfigurationModel so we can reset it after each test # Explicitly import the cache from ConfigurationModel so we can reset it after each test
from config_models.models import cache from config_models.models import cache
from embargo.forms import EmbargoedCourseForm, EmbargoedStateForm, IPFilterForm from embargo.models import IPFilter
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter from embargo.forms import RestrictedCourseForm, IPFilterForm
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import TEST_DATA_MOCK_MODULESTORE
class EmbargoCourseFormTest(ModuleStoreTestCase): class RestrictedCourseFormTest(ModuleStoreTestCase):
"""Test the course form properly validates course IDs""" """Test the course form properly validates course IDs"""
def setUp(self): def test_save_valid_data(self):
super(EmbargoCourseFormTest, self).setUp() course = CourseFactory.create()
self.course = CourseFactory.create() data = {
self.true_form_data = {'course_id': self.course.id.to_deprecated_string(), 'embargoed': True} 'course_key': unicode(course.id),
self.false_form_data = {'course_id': self.course.id.to_deprecated_string(), 'embargoed': False} 'enroll_msg_key': 'default',
'access_msg_key': 'default'
def test_embargo_course(self): }
self.assertFalse(EmbargoedCourse.is_embargoed(self.course.id)) form = RestrictedCourseForm(data=data)
# Test adding embargo to this course
form = EmbargoedCourseForm(data=self.true_form_data)
# Validation should work
self.assertTrue(form.is_valid())
form.save()
# Check that this course is embargoed
self.assertTrue(EmbargoedCourse.is_embargoed(self.course.id))
def test_repeat_course(self):
# Initially course shouldn't be authorized
self.assertFalse(EmbargoedCourse.is_embargoed(self.course.id))
# Test authorizing the course, which should totally work
form = EmbargoedCourseForm(data=self.true_form_data)
# Validation should work
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
form.save()
# Check that this course is authorized
self.assertTrue(EmbargoedCourse.is_embargoed(self.course.id))
# Now make a new course authorization with the same course id that tries to turn email off
form = EmbargoedCourseForm(data=self.false_form_data)
# Validation should not work because course_id field is unique
self.assertFalse(form.is_valid())
self.assertEquals(
"Embargoed course with this Course id already exists.",
form._errors['course_id'][0] # pylint: disable=protected-access
)
with self.assertRaisesRegexp(ValueError, "The EmbargoedCourse could not be created because the data didn't validate."):
form.save()
# Course should still be authorized (invalid attempt had no effect)
self.assertTrue(EmbargoedCourse.is_embargoed(self.course.id))
def test_form_typo(self): def test_invalid_course_key(self):
# Munge course id # Invalid format for the course key
bad_id = self.course.id.to_deprecated_string() + '_typo' form = RestrictedCourseForm(data={'course_key': 'not/valid'})
self._assert_course_field_error(form)
form_data = {'course_id': bad_id, 'embargoed': True} def test_course_not_found(self):
form = EmbargoedCourseForm(data=form_data) course_key = CourseLocator(org='test', course='test', run='test')
# Validation shouldn't work form = RestrictedCourseForm(data={'course_key': course_key})
self.assertFalse(form.is_valid()) self._assert_course_field_error(form)
msg = 'COURSE NOT FOUND' def _assert_course_field_error(self, form):
self.assertIn(msg, form._errors['course_id'][0]) # pylint: disable=protected-access
with self.assertRaisesRegexp(ValueError, "The EmbargoedCourse could not be created because the data didn't validate."):
form.save()
def test_invalid_location(self):
# Munge course id
bad_id = self.course.id.to_deprecated_string().split('/')[-1]
form_data = {'course_id': bad_id, 'embargoed': True}
form = EmbargoedCourseForm(data=form_data)
# Validation shouldn't work # Validation shouldn't work
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
msg = 'COURSE NOT FOUND' msg = 'COURSE NOT FOUND'
self.assertIn(msg, form._errors['course_id'][0]) # pylint: disable=protected-access self.assertIn(msg, form._errors['course_key'][0]) # pylint: disable=protected-access
with self.assertRaisesRegexp(ValueError, "The EmbargoedCourse could not be created because the data didn't validate."):
form.save()
with self.assertRaisesRegexp(ValueError, "The RestrictedCourse could not be created because the data didn't validate."):
class EmbargoedStateFormTest(TestCase):
"""Test form for adding new states"""
def setUp(self):
# Explicitly clear the cache, since ConfigurationModel relies on the cache
cache.clear()
def tearDown(self):
# Explicitly clear ConfigurationModel's cache so tests have a clear cache
# and don't interfere with each other
cache.clear()
def test_add_valid_states(self):
# test adding valid two letter states
# case and spacing should not matter
form_data = {'embargoed_countries': 'cu, Sy , US'}
form = EmbargoedStateForm(data=form_data)
self.assertTrue(form.is_valid())
form.save() form.save()
current_embargoes = EmbargoedState.current().embargoed_countries_list
for country in ["CU", "SY", "US"]:
self.assertIn(country, current_embargoes)
# Test clearing by adding an empty list is OK too
form_data = {'embargoed_countries': ''}
form = EmbargoedStateForm(data=form_data)
self.assertTrue(form.is_valid())
form.save()
self.assertTrue(len(EmbargoedState.current().embargoed_countries_list) == 0)
def test_add_invalid_states(self):
# test adding invalid codes
# xx is not valid
# usa is not valid
form_data = {'embargoed_countries': 'usa, xx'}
form = EmbargoedStateForm(data=form_data)
self.assertFalse(form.is_valid())
msg = 'COULD NOT PARSE COUNTRY CODE(S) FOR: {0}'.format([u'USA', u'XX'])
msg += ' Please check the list of country codes and verify your entries.'
self.assertEquals(msg, form._errors['embargoed_countries'][0]) # pylint: disable=protected-access
with self.assertRaisesRegexp(ValueError, "The EmbargoedState could not be created because the data didn't validate."):
form.save()
self.assertFalse('USA' in EmbargoedState.current().embargoed_countries_list)
self.assertFalse('XX' in EmbargoedState.current().embargoed_countries_list)
class IPFilterFormTest(TestCase): class IPFilterFormTest(TestCase):
......
"""
Tests for EmbargoMiddleware with CountryAccessRules
"""
import unittest
from mock import patch
import ddt
from django.core.urlresolvers import reverse
from django.conf import settings
from django.core.cache import cache as django_cache
from util.testing import UrlResetMixin
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, mixed_store_config
)
from config_models.models import cache as config_cache
from embargo.models import RestrictedCourse, IPFilter
from embargo.test_utils import restrict_course
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False)
@ddt.ddt
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase):
"""Tests of embargo middleware country access rules.
There are detailed unit tests for the rule logic in
`test_api.py`; here, we're mainly testing the integration
with middleware
"""
USERNAME = 'fred'
PASSWORD = 'secret'
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
def setUp(self):
super(EmbargoMiddlewareAccessTests, self).setUp('embargo')
self.user = UserFactory(username=self.USERNAME, password=self.PASSWORD)
self.course = CourseFactory.create()
self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.courseware_url = reverse(
'course_root',
kwargs={'course_id': unicode(self.course.id)}
)
self.non_courseware_url = reverse('dashboard')
# Clear the cache to avoid interference between tests
django_cache.clear()
config_cache.clear()
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
def test_blocked(self):
with restrict_course(self.course.id, access_point='courseware') as redirect_url:
response = self.client.get(self.courseware_url)
self.assertRedirects(response, redirect_url)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
def test_allowed(self):
# Add the course to the list of restricted courses
# but don't create any access rules
RestrictedCourse.objects.create(course_key=self.course.id)
# Expect that we can access courseware
response = self.client.get(self.courseware_url)
self.assertEqual(response.status_code, 200)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
def test_non_courseware_url(self):
with restrict_course(self.course.id):
response = self.client.get(self.non_courseware_url)
self.assertEqual(response.status_code, 200)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
@ddt.data(
# request_ip, blacklist, whitelist, is_enabled, allow_access
('173.194.123.35', ['173.194.123.35'], [], True, False),
('173.194.123.35', ['173.194.0.0/16'], [], True, False),
('173.194.123.35', ['127.0.0.0/32', '173.194.0.0/16'], [], True, False),
('173.195.10.20', ['173.194.0.0/16'], [], True, True),
('173.194.123.35', ['173.194.0.0/16'], ['173.194.0.0/16'], True, False),
('173.194.123.35', [], ['173.194.0.0/16'], True, True),
('192.178.2.3', [], ['173.194.0.0/16'], True, True),
('173.194.123.35', ['173.194.123.35'], [], False, True),
)
@ddt.unpack
def test_ip_access_rules(self, request_ip, blacklist, whitelist, is_enabled, allow_access):
# Ensure that IP blocking works for anonymous users
self.client.logout()
# Set up the IP rules
IPFilter.objects.create(
blacklist=", ".join(blacklist),
whitelist=", ".join(whitelist),
enabled=is_enabled
)
# Check that access is enforced
response = self.client.get(
"/",
HTTP_X_FORWARDED_FOR=request_ip,
REMOTE_ADDR=request_ip
)
if allow_access:
self.assertEqual(response.status_code, 200)
else:
redirect_url = reverse(
'embargo_blocked_message',
kwargs={
'access_point': 'courseware',
'message_key': 'embargo'
}
)
self.assertRedirects(response, redirect_url)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
@ddt.data(
('courseware', 'default'),
('courseware', 'embargo'),
('enrollment', 'default'),
('enrollment', 'embargo')
)
@ddt.unpack
def test_always_allow_access_to_embargo_messages(self, access_point, msg_key):
# Blacklist an IP address
IPFilter.objects.create(
blacklist="192.168.10.20",
enabled=True
)
url = reverse(
'embargo_blocked_message',
kwargs={
'access_point': access_point,
'message_key': msg_key
}
)
response = self.client.get(
url,
HTTP_X_FORWARDED_FOR="192.168.10.20",
REMOTE_ADDR="192.168.10.20"
)
self.assertEqual(response.status_code, 200)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True})
def test_whitelist_ip_skips_country_access_checks(self):
# Whitelist an IP address
IPFilter.objects.create(
whitelist="192.168.10.20",
enabled=True
)
# Set up country access rules so the user would
# be restricted from the course.
with restrict_course(self.course.id):
# Make a request from the whitelisted IP address
response = self.client.get(
self.courseware_url,
HTTP_X_FORWARDED_FOR="192.168.10.20",
REMOTE_ADDR="192.168.10.20"
)
# Expect that we were still able to access the page,
# even though we would have been blocked by country
# access rules.
self.assertEqual(response.status_code, 200)
...@@ -32,7 +32,7 @@ class CourseAccessMessageViewTest(UrlResetMixin, TestCase): ...@@ -32,7 +32,7 @@ class CourseAccessMessageViewTest(UrlResetMixin, TestCase):
""" """
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(CourseAccessMessageViewTest, self).setUp('embargo') super(CourseAccessMessageViewTest, self).setUp('embargo')
......
...@@ -283,7 +283,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -283,7 +283,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
EMAIL = "bob@example.com" EMAIL = "bob@example.com"
PASSWORD = "edx" PASSWORD = "edx"
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
""" Create a course and user, then log in. """ """ Create a course and user, then log in. """
super(EnrollmentEmbargoTest, self).setUp('embargo') super(EnrollmentEmbargoTest, self).setUp('embargo')
...@@ -291,7 +291,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -291,7 +291,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD) self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD) self.client.login(username=self.USERNAME, password=self.PASSWORD)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_change_enrollment_restrict(self): def test_embargo_change_enrollment_restrict(self):
url = reverse('courseenrollments') url = reverse('courseenrollments')
data = json.dumps({ data = json.dumps({
...@@ -315,7 +315,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -315,7 +315,7 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
# Verify that we were not enrolled # Verify that we were not enrolled
self.assertEqual(self._get_enrollments(), []) self.assertEqual(self._get_enrollments(), [])
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_change_enrollment_allow(self): def test_embargo_change_enrollment_allow(self):
url = reverse('courseenrollments') url = reverse('courseenrollments')
data = json.dumps({ data = json.dumps({
......
...@@ -26,7 +26,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -26,7 +26,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
EMAIL = "bob@example.com" EMAIL = "bob@example.com"
PASSWORD = "edx" PASSWORD = "edx"
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
""" Create a course and user, then log in. """ """ Create a course and user, then log in. """
super(EnrollmentTest, self).setUp('embargo') super(EnrollmentTest, self).setUp('embargo')
...@@ -134,7 +134,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -134,7 +134,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
else: else:
self.assertFalse(mock_update_email_opt_in.called) self.assertFalse(mock_update_email_opt_in.called)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self): def test_embargo_restrict(self):
# When accessing the course from an embargoed country, # When accessing the course from an embargoed country,
# we should be blocked. # we should be blocked.
...@@ -147,7 +147,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -147,7 +147,7 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id) is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
self.assertFalse(is_enrolled) self.assertFalse(is_enrolled)
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_allow(self): def test_embargo_allow(self):
response = self._change_enrollment('enroll') response = self._change_enrollment('enroll')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
......
...@@ -168,21 +168,6 @@ def index(request, extra_context=None, user=AnonymousUser()): ...@@ -168,21 +168,6 @@ def index(request, extra_context=None, user=AnonymousUser()):
return render_to_response('index.html', context) return render_to_response('index.html', context)
def embargo(_request):
"""
Render the embargo page.
Explains to the user why they are not able to access a particular embargoed course.
Tries to use the themed version, but fall back to the default if not found.
"""
try:
if settings.FEATURES["USE_CUSTOM_THEME"]:
return render_to_response("static_templates/theme-embargo.html")
except TopLevelLookupException:
pass
return render_to_response("static_templates/embargo.html")
def process_survey_link(survey_link, user): def process_survey_link(survey_link, user):
""" """
If {UNIQUE_ID} appears in the link, replace it with a unique id for the user. If {UNIQUE_ID} appears in the link, replace it with a unique id for the user.
......
...@@ -29,14 +29,14 @@ THIRD_PARTY_AUTH_CONFIGURED = ( ...@@ -29,14 +29,14 @@ THIRD_PARTY_AUTH_CONFIGURED = (
@unittest.skipUnless(THIRD_PARTY_AUTH_CONFIGURED, "Third party auth must be configured") @unittest.skipUnless(THIRD_PARTY_AUTH_CONFIGURED, "Third party auth must be configured")
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
@ddt.ddt @ddt.ddt
class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase): class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
"""Test that the pipeline auto-enrolls students upon successful authentication. """ """Test that the pipeline auto-enrolls students upon successful authentication. """
BACKEND_NAME = "google-oauth2" BACKEND_NAME = "google-oauth2"
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
"""Create a test course and user. """ """Create a test course and user. """
super(PipelineEnrollmentTest, self).setUp('embargo') super(PipelineEnrollmentTest, self).setUp('embargo')
...@@ -129,7 +129,7 @@ class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase): ...@@ -129,7 +129,7 @@ class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
self.assertEqual(result, {}) self.assertEqual(result, {})
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id)) self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_blocked_by_embargo(self): def test_blocked_by_embargo(self):
strategy = self._fake_strategy() strategy = self._fake_strategy()
strategy.session_set('enroll_course_id', unicode(self.course.id)) strategy.session_set('enroll_course_id', unicode(self.course.id))
......
...@@ -1585,7 +1585,7 @@ class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase): ...@@ -1585,7 +1585,7 @@ class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase):
USERNAME = 'bob' USERNAME = 'bob'
PASSWORD = 'test' PASSWORD = 'test'
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(RedeemCodeEmbargoTests, self).setUp('embargo') super(RedeemCodeEmbargoTests, self).setUp('embargo')
self.course = CourseFactory.create() self.course = CourseFactory.create()
...@@ -1594,7 +1594,7 @@ class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase): ...@@ -1594,7 +1594,7 @@ class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase):
self.assertTrue(result, msg="Could not log in") self.assertTrue(result, msg="Could not log in")
@ddt.data('get', 'post') @ddt.data('get', 'post')
@patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_registration_code_redemption_embargo(self, method): def test_registration_code_redemption_embargo(self, method):
# Create a valid registration code # Create a valid registration code
reg_code = CourseRegistrationCode.objects.create( reg_code = CourseRegistrationCode.objects.create(
......
...@@ -382,7 +382,7 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase) ...@@ -382,7 +382,7 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase)
EMAIL = "bob@example.com" EMAIL = "bob@example.com"
PASSWORD = "password" PASSWORD = "password"
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(StudentAccountLoginAndRegistrationTest, self).setUp('embargo') super(StudentAccountLoginAndRegistrationTest, self).setUp('embargo')
...@@ -551,7 +551,7 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase) ...@@ -551,7 +551,7 @@ class StudentAccountLoginAndRegistrationTest(UrlResetMixin, ModuleStoreTestCase)
response = self.client.get(reverse("account_login"), {"course_id": unicode(course.id)}) response = self.client.get(reverse("account_login"), {"course_id": unicode(course.id)})
self._assert_third_party_auth_data(response, None, expected_providers) self._assert_third_party_auth_data(response, None, expected_providers)
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_third_party_auth_enrollment_embargo(self): def test_third_party_auth_enrollment_embargo(self):
course = CourseFactory.create() course = CourseFactory.create()
......
...@@ -74,7 +74,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase): ...@@ -74,7 +74,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase):
YESTERDAY = NOW - timedelta(days=1) YESTERDAY = NOW - timedelta(days=1)
TOMORROW = NOW + timedelta(days=1) TOMORROW = NOW + timedelta(days=1)
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self): def setUp(self):
super(TestPayAndVerifyView, self).setUp('embargo') super(TestPayAndVerifyView, self).setUp('embargo')
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD) self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
...@@ -625,7 +625,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase): ...@@ -625,7 +625,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase):
self.assertContains(response, "verification deadline") self.assertContains(response, "verification deadline")
self.assertContains(response, "Jan 02, 1999 at 00:00 UTC") self.assertContains(response, "Jan 02, 1999 at 00:00 UTC")
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self): def test_embargo_restrict(self):
course = self._create_course("verified") course = self._create_course("verified")
with restrict_course(course.id) as redirect_url: with restrict_course(course.id) as redirect_url:
...@@ -634,7 +634,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase): ...@@ -634,7 +634,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase):
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302) response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302)
self.assertRedirects(response, redirect_url) self.assertRedirects(response, redirect_url)
@mock.patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) @mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_allow(self): def test_embargo_allow(self):
course = self._create_course("verified") course = self._create_course("verified")
self._get_page('verify_student_start_flow', course.id) self._get_page('verify_student_start_flow', course.id)
......
...@@ -268,16 +268,10 @@ FEATURES = { ...@@ -268,16 +268,10 @@ FEATURES = {
# Hide any Personally Identifiable Information from application logs # Hide any Personally Identifiable Information from application logs
'SQUELCH_PII_IN_LOGS': True, 'SQUELCH_PII_IN_LOGS': True,
# Toggles the embargo functionality, which enable embargoing for particular courses # Toggles the embargo functionality, which blocks users from
# the site or courses based on their location.
'EMBARGO': False, 'EMBARGO': False,
# Toggles the embargo site functionality, which enable embargoing for the whole site
'SITE_EMBARGOED': False,
# Toggle whether to replace the current embargo implementation with
# the more flexible "country access" feature.
'ENABLE_COUNTRY_ACCESS': False,
# Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means # Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means
# that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the # that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the
# defaults, so that we maintain current behavior # defaults, so that we maintain current behavior
......
...@@ -67,8 +67,6 @@ urlpatterns = ('', # nopep8 ...@@ -67,8 +67,6 @@ urlpatterns = ('', # nopep8
url(r'^i18n/', include('django.conf.urls.i18n')), url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^embargo$', 'student.views.embargo', name="embargo"),
# Feedback Form endpoint # Feedback Form endpoint
url(r'^submit_feedback$', 'util.views.submit_feedback'), url(r'^submit_feedback$', 'util.views.submit_feedback'),
...@@ -494,8 +492,8 @@ urlpatterns += ( ...@@ -494,8 +492,8 @@ urlpatterns += (
url(r'^shoppingcart/', include('shoppingcart.urls')), url(r'^shoppingcart/', include('shoppingcart.urls')),
) )
# Country access (embargo) # Embargo
if settings.FEATURES.get('ENABLE_COUNTRY_ACCESS'): if settings.FEATURES.get('EMBARGO'):
urlpatterns += ( urlpatterns += (
url(r'^embargo/', include('embargo.urls')), url(r'^embargo/', include('embargo.urls')),
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment