Merge remote-tracking branch 'origin/release'
Conflicts: cms/envs/common.py lms/envs/common.py
This commit is contained in:
@@ -81,6 +81,9 @@ FEATURES = {
|
||||
|
||||
# Hide any Personally Identifiable Information from application logs
|
||||
'SQUELCH_PII_IN_LOGS': False,
|
||||
|
||||
# Toggles embargo functionality
|
||||
'EMBARGO': False,
|
||||
}
|
||||
ENABLE_JASMINE = False
|
||||
|
||||
@@ -99,6 +102,9 @@ sys.path.append(PROJECT_ROOT / 'djangoapps')
|
||||
sys.path.append(COMMON_ROOT / 'djangoapps')
|
||||
sys.path.append(COMMON_ROOT / 'lib')
|
||||
|
||||
# For geolocation ip database
|
||||
GEOIP_PATH = REPO_ROOT / "common/static/data/geoip/GeoIP.dat"
|
||||
|
||||
|
||||
############################# WEB CONFIGURATION #############################
|
||||
# This is where we stick our compiled template files.
|
||||
@@ -185,6 +191,8 @@ MIDDLEWARE_CLASSES = (
|
||||
# Allows us to dark-launch particular languages
|
||||
'dark_lang.middleware.DarkLangMiddleware',
|
||||
|
||||
'embargo.middleware.EmbargoMiddleware',
|
||||
|
||||
# Detects user-requested locale from 'accept-language' header in http request
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
|
||||
@@ -467,6 +475,8 @@ INSTALLED_APPS = (
|
||||
# User preferences
|
||||
'user_api',
|
||||
'django_openid_auth',
|
||||
|
||||
'embargo',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -196,3 +196,6 @@ FEATURES['ENABLE_SERVICE_STATUS'] = True
|
||||
|
||||
# This is to disable a test under the common directory that will not pass when run under CMS
|
||||
FEATURES['DISABLE_RESET_EMAIL_TEST'] = True
|
||||
|
||||
# Toggles embargo on for testing
|
||||
FEATURES['EMBARGO'] = True
|
||||
|
||||
@@ -54,6 +54,7 @@ urlpatterns += patterns(
|
||||
# ajax view that actually does the work
|
||||
url(r'^login_post$', 'student.views.login_user', name='login_post'),
|
||||
url(r'^logout$', 'student.views.logout_user', name='logout'),
|
||||
url(r'^embargo$', 'student.views.embargo', name="embargo"),
|
||||
)
|
||||
|
||||
# restful api
|
||||
|
||||
0
common/djangoapps/embargo/__init__.py
Normal file
0
common/djangoapps/embargo/__init__.py
Normal file
64
common/djangoapps/embargo/admin.py
Normal file
64
common/djangoapps/embargo/admin.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Django admin page for embargo models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
import textwrap
|
||||
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
from embargo.forms import EmbargoedCourseForm, EmbargoedStateForm, IPFilterForm
|
||||
|
||||
|
||||
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': ('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):
|
||||
"""Admin for blacklisting/whitelisting specific IP addresses"""
|
||||
form = IPFilterForm
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('whitelist', 'blacklist'),
|
||||
'description': textwrap.dedent("""Enter specific IP addresses to explicitly
|
||||
whitelist (not block) or blacklist (block) in the appropriate box below.
|
||||
Separate IP addresses with a comma. Do not surround with quotes.
|
||||
""")
|
||||
}),
|
||||
)
|
||||
|
||||
admin.site.register(EmbargoedCourse, EmbargoedCourseAdmin)
|
||||
admin.site.register(EmbargoedState, EmbargoedStateAdmin)
|
||||
admin.site.register(IPFilter, IPFilterAdmin)
|
||||
0
common/djangoapps/embargo/fixtures/__init__.py
Normal file
0
common/djangoapps/embargo/fixtures/__init__.py
Normal file
25
common/djangoapps/embargo/fixtures/country_codes.py
Normal file
25
common/djangoapps/embargo/fixtures/country_codes.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
List of valid ISO 3166-1 Alpha-2 country codes, used for
|
||||
validating entries on entered country codes on django-admin page.
|
||||
"""
|
||||
|
||||
COUNTRY_CODES = set([
|
||||
"AC", "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT",
|
||||
"AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BM",
|
||||
"BN", "BO", "BR", "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG",
|
||||
"CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CX", "CY", "CZ", "DE",
|
||||
"DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "ER", "ES", "ET", "FI", "FJ", "FK",
|
||||
"FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN",
|
||||
"GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU",
|
||||
"ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", "JE", "JM", "JO", "JP",
|
||||
"KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", "LB", "LC",
|
||||
"LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MG", "MH",
|
||||
"MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX",
|
||||
"MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ",
|
||||
"OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PT", "PW", "PY",
|
||||
"QA", "RE", "RO", "RS", "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI",
|
||||
"SJ", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TA", "TC", "TD",
|
||||
"TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ",
|
||||
"UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF",
|
||||
"WS", "YE", "YT", "ZA", "ZM", "ZW"
|
||||
])
|
||||
125
common/djangoapps/embargo/forms.py
Normal file
125
common/djangoapps/embargo/forms.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
Defines forms for providing validation of embargo admin details.
|
||||
"""
|
||||
|
||||
from django import forms
|
||||
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
from embargo.fixtures.country_codes import COUNTRY_CODES
|
||||
|
||||
import socket
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
class EmbargoedCourseForm(forms.ModelForm): # pylint: disable=incomplete-protocol
|
||||
"""Form providing validation of entered Course IDs."""
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring
|
||||
model = EmbargoedCourse
|
||||
|
||||
def clean_course_id(self):
|
||||
"""Validate the course id"""
|
||||
course_id = self.cleaned_data["course_id"]
|
||||
|
||||
# Try to get the course. If this returns None, it's not a real course
|
||||
try:
|
||||
course = modulestore().get_course(course_id)
|
||||
except ValueError:
|
||||
msg = 'COURSE NOT FOUND'
|
||||
msg += u' --- Entered course id was: "{0}". '.format(course_id)
|
||||
msg += 'Please recheck that you have supplied a valid course id.'
|
||||
raise forms.ValidationError(msg)
|
||||
if not course:
|
||||
msg = 'COURSE NOT FOUND'
|
||||
msg += u' --- Entered course id was: "{0}". '.format(course_id)
|
||||
msg += 'Please recheck that you have supplied a valid course id.'
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
return course_id
|
||||
|
||||
|
||||
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
|
||||
"""Form validating entry of IP addresses"""
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring
|
||||
model = IPFilter
|
||||
|
||||
def _is_valid_ipv4(self, address):
|
||||
"""Whether or not address is a valid ipv4 address"""
|
||||
try:
|
||||
# Is this an ipv4 address?
|
||||
socket.inet_pton(socket.AF_INET, address)
|
||||
except socket.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_valid_ipv6(self, address):
|
||||
"""Whether or not address is a valid ipv6 address"""
|
||||
try:
|
||||
# Is this an ipv6 address?
|
||||
socket.inet_pton(socket.AF_INET6, address)
|
||||
except socket.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _valid_ip_addresses(self, addresses):
|
||||
"""
|
||||
Checks if a csv string of IP addresses contains valid values.
|
||||
|
||||
If not, raises a ValidationError.
|
||||
"""
|
||||
if addresses == '':
|
||||
return ''
|
||||
error_addresses = []
|
||||
for addr in addresses.split(','):
|
||||
address = addr.strip()
|
||||
if not (self._is_valid_ipv4(address) or self._is_valid_ipv6(address)):
|
||||
error_addresses.append(address)
|
||||
if error_addresses:
|
||||
msg = 'Invalid IP Address(es): {0}'.format(error_addresses)
|
||||
msg += ' Please fix the error(s) and try again.'
|
||||
raise forms.ValidationError(msg)
|
||||
|
||||
return addresses
|
||||
|
||||
def clean_whitelist(self):
|
||||
"""Validates the whitelist"""
|
||||
whitelist = self.cleaned_data["whitelist"]
|
||||
return self._valid_ip_addresses(whitelist)
|
||||
|
||||
def clean_blacklist(self):
|
||||
"""Validates the blacklist"""
|
||||
blacklist = self.cleaned_data["blacklist"]
|
||||
return self._valid_ip_addresses(blacklist)
|
||||
54
common/djangoapps/embargo/middleware.py
Normal file
54
common/djangoapps/embargo/middleware.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Middleware for embargoing courses.
|
||||
|
||||
IMPORTANT NOTE: This code WILL NOT WORK if you have a misconfigured proxy
|
||||
server. If you are configuring embargo functionality, or if you are
|
||||
experiencing mysterious problems with embargoing, please check that your
|
||||
reverse proxy is setting any of the well known client IP address headers (ex.,
|
||||
HTTP_X_FORWARDED_FOR).
|
||||
"""
|
||||
|
||||
import pygeoip
|
||||
|
||||
from django.core.exceptions import MiddlewareNotUsed
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from ipware.ip import get_ip
|
||||
from util.request import course_id_from_url
|
||||
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
|
||||
|
||||
class EmbargoMiddleware(object):
|
||||
"""
|
||||
Middleware for embargoing courses
|
||||
|
||||
This is configured by creating ``EmbargoedCourse``, ``EmbargoedState``, and
|
||||
optionally ``IPFilter`` rows in the database, using the django admin site.
|
||||
"""
|
||||
def __init__(self):
|
||||
# If embargoing is turned off, make this middleware do nothing
|
||||
if not settings.FEATURES.get('EMBARGO', False):
|
||||
raise MiddlewareNotUsed()
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Processes embargo requests
|
||||
"""
|
||||
url = request.path
|
||||
course_id = course_id_from_url(url)
|
||||
|
||||
# If they're trying to access a course that cares about embargoes
|
||||
if EmbargoedCourse.is_embargoed(course_id):
|
||||
# If we're having performance issues, add caching here
|
||||
ip_addr = get_ip(request)
|
||||
|
||||
# if blacklisted, immediately fail
|
||||
if ip_addr in IPFilter.current().blacklist_ips:
|
||||
return redirect('embargo')
|
||||
|
||||
country_code_from_ip = pygeoip.GeoIP(settings.GEOIP_PATH).country_code_by_addr(ip_addr)
|
||||
is_embargoed = country_code_from_ip in EmbargoedState.current().embargoed_countries_list
|
||||
# Fail if country is embargoed and the ip address isn't explicitly whitelisted
|
||||
if is_embargoed and ip_addr not in IPFilter.current().whitelist_ips:
|
||||
return redirect('embargo')
|
||||
114
common/djangoapps/embargo/migrations/0001_initial.py
Normal file
114
common/djangoapps/embargo/migrations/0001_initial.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'EmbargoedCourse'
|
||||
db.create_table('embargo_embargoedcourse', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, db_index=True)),
|
||||
('embargoed', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
))
|
||||
db.send_create_signal('embargo', ['EmbargoedCourse'])
|
||||
|
||||
# Adding model 'EmbargoedState'
|
||||
db.create_table('embargo_embargoedstate', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('embargoed_countries', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
))
|
||||
db.send_create_signal('embargo', ['EmbargoedState'])
|
||||
|
||||
# Adding model 'IPFilter'
|
||||
db.create_table('embargo_ipfilter', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
('whitelist', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
('blacklist', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
))
|
||||
db.send_create_signal('embargo', ['IPFilter'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'EmbargoedCourse'
|
||||
db.delete_table('embargo_embargoedcourse')
|
||||
|
||||
# Deleting model 'EmbargoedState'
|
||||
db.delete_table('embargo_embargoedstate')
|
||||
|
||||
# Deleting model 'IPFilter'
|
||||
db.delete_table('embargo_ipfilter')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'embargo.embargoedcourse': {
|
||||
'Meta': {'object_name': 'EmbargoedCourse'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
|
||||
'embargoed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'embargo.embargoedstate': {
|
||||
'Meta': {'object_name': 'EmbargoedState'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'embargoed_countries': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'embargo.ipfilter': {
|
||||
'Meta': {'object_name': 'IPFilter'},
|
||||
'blacklist': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'whitelist': ('django.db.models.fields.TextField', [], {'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['embargo']
|
||||
0
common/djangoapps/embargo/migrations/__init__.py
Normal file
0
common/djangoapps/embargo/migrations/__init__.py
Normal file
98
common/djangoapps/embargo/models.py
Normal file
98
common/djangoapps/embargo/models.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Models for embargoing visits to certain courses by IP address.
|
||||
|
||||
WE'RE USING MIGRATIONS!
|
||||
|
||||
If you make changes to this model, be sure to create an appropriate migration
|
||||
file and check it in at the same time as your model changes. To do that,
|
||||
|
||||
1. Go to the edx-platform dir
|
||||
2. ./manage.py lms schemamigration embargo --auto description_of_your_change
|
||||
3. Add the migration file created in edx-platform/common/djangoapps/embargo/migrations/
|
||||
"""
|
||||
from django.db import models
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
class EmbargoedCourse(models.Model):
|
||||
"""
|
||||
Enable course embargo on a course-by-course basis.
|
||||
"""
|
||||
# The course to embargo
|
||||
course_id = models.CharField(max_length=255, db_index=True, unique=True)
|
||||
|
||||
# Whether or not to embargo
|
||||
embargoed = models.BooleanField(default=False)
|
||||
|
||||
@classmethod
|
||||
def is_embargoed(cls, course_id):
|
||||
"""
|
||||
Returns whether or not the given course id is embargoed.
|
||||
|
||||
If course has not been explicitly embargoed, returns False.
|
||||
"""
|
||||
try:
|
||||
record = cls.objects.get(course_id=course_id)
|
||||
return record.embargoed
|
||||
except cls.DoesNotExist:
|
||||
return False
|
||||
|
||||
def __unicode__(self):
|
||||
not_em = "Not "
|
||||
if self.embargoed:
|
||||
not_em = ""
|
||||
return u"Course '{}' is {}Embargoed".format(self.course_id, not_em)
|
||||
|
||||
|
||||
class EmbargoedState(ConfigurationModel):
|
||||
"""
|
||||
Register countries to be embargoed.
|
||||
"""
|
||||
# The countries to embargo
|
||||
embargoed_countries = models.TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of country codes that fall under U.S. embargo restrictions"
|
||||
)
|
||||
|
||||
@property
|
||||
def embargoed_countries_list(self):
|
||||
"""
|
||||
Return a list of upper case country codes
|
||||
"""
|
||||
if self.embargoed_countries == '':
|
||||
return []
|
||||
return [country.strip().upper() for country in self.embargoed_countries.split(',')] # pylint: disable=no-member
|
||||
|
||||
|
||||
class IPFilter(ConfigurationModel):
|
||||
"""
|
||||
Register specific IP addresses to explicitly block or unblock.
|
||||
"""
|
||||
whitelist = models.TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of IP addresses that should not fall under embargo restrictions."
|
||||
)
|
||||
|
||||
blacklist = models.TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of IP addresses that should fall under embargo restrictions."
|
||||
)
|
||||
|
||||
@property
|
||||
def whitelist_ips(self):
|
||||
"""
|
||||
Return a list of valid IP addresses to whitelist
|
||||
"""
|
||||
if self.whitelist == '':
|
||||
return []
|
||||
return [addr.strip() for addr in self.whitelist.split(',')] # pylint: disable=no-member
|
||||
|
||||
@property
|
||||
def blacklist_ips(self):
|
||||
"""
|
||||
Return a list of valid IP addresses to blacklist
|
||||
"""
|
||||
if self.blacklist == '':
|
||||
return []
|
||||
return [addr.strip() for addr in self.blacklist.split(',')] # pylint: disable=no-member
|
||||
198
common/djangoapps/embargo/tests/test_forms.py
Normal file
198
common/djangoapps/embargo/tests/test_forms.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Unit tests for embargo app admin forms.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
# Explicitly import the cache from ConfigurationModel so we can reset it after each test
|
||||
from config_models.models import cache
|
||||
from embargo.forms import EmbargoedCourseForm, EmbargoedStateForm, IPFilterForm
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class EmbargoCourseFormTest(ModuleStoreTestCase):
|
||||
"""Test the course form properly validates course IDs"""
|
||||
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
self.true_form_data = {'course_id': self.course.id, 'embargoed': True}
|
||||
self.false_form_data = {'course_id': self.course.id, 'embargoed': False}
|
||||
|
||||
def test_embargo_course(self):
|
||||
self.assertFalse(EmbargoedCourse.is_embargoed(self.course.id))
|
||||
# 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())
|
||||
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):
|
||||
# Munge course id
|
||||
bad_id = self.course.id + '_typo'
|
||||
|
||||
form_data = {'course_id': bad_id, 'embargoed': True}
|
||||
form = EmbargoedCourseForm(data=form_data)
|
||||
# Validation shouldn't work
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
msg = 'COURSE NOT FOUND'
|
||||
msg += u' --- Entered course id was: "{0}". '.format(bad_id)
|
||||
msg += 'Please recheck that you have supplied a valid course id.'
|
||||
self.assertEquals(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.split('/')[-1]
|
||||
|
||||
form_data = {'course_id': bad_id, 'embargoed': True}
|
||||
form = EmbargoedCourseForm(data=form_data)
|
||||
# Validation shouldn't work
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
msg = 'COURSE NOT FOUND'
|
||||
msg += u' --- Entered course id was: "{0}". '.format(bad_id)
|
||||
msg += 'Please recheck that you have supplied a valid course id.'
|
||||
self.assertEquals(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()
|
||||
|
||||
|
||||
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()
|
||||
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):
|
||||
"""Test form for adding [black|white]list IP addresses"""
|
||||
|
||||
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_ips(self):
|
||||
# test adding valid ip addresses
|
||||
# should be able to do both ipv4 and ipv6
|
||||
# spacing should not matter
|
||||
form_data = {
|
||||
'whitelist': '127.0.0.1, 2003:dead:beef:4dad:23:46:bb:101',
|
||||
'blacklist': ' 18.244.1.5 , 2002:c0a8:101::42, 18.36.22.1'
|
||||
}
|
||||
form = IPFilterForm(data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
form.save()
|
||||
whitelist = IPFilter.current().whitelist_ips
|
||||
blacklist = IPFilter.current().blacklist_ips
|
||||
for addr in '127.0.0.1, 2003:dead:beef:4dad:23:46:bb:101'.split(','):
|
||||
self.assertIn(addr.strip(), whitelist)
|
||||
for addr in '18.244.1.5, 2002:c0a8:101::42, 18.36.22.1'.split(','):
|
||||
self.assertIn(addr.strip(), blacklist)
|
||||
|
||||
# Test clearing by adding an empty list is OK too
|
||||
form_data = {
|
||||
'whitelist': '',
|
||||
'blacklist': ''
|
||||
}
|
||||
form = IPFilterForm(data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
form.save()
|
||||
self.assertTrue(len(IPFilter.current().whitelist) == 0)
|
||||
self.assertTrue(len(IPFilter.current().blacklist) == 0)
|
||||
|
||||
def test_add_invalid_ips(self):
|
||||
# test adding invalid ip addresses
|
||||
form_data = {
|
||||
'whitelist': '.0.0.1, :dead:beef:::',
|
||||
'blacklist': ' 18.244.* , 999999:c0a8:101::42'
|
||||
}
|
||||
form = IPFilterForm(data=form_data)
|
||||
self.assertFalse(form.is_valid())
|
||||
|
||||
wmsg = "Invalid IP Address(es): [u'.0.0.1', u':dead:beef:::'] Please fix the error(s) and try again."
|
||||
self.assertEquals(wmsg, form._errors['whitelist'][0]) # pylint: disable=protected-access
|
||||
bmsg = "Invalid IP Address(es): [u'18.244.*', u'999999:c0a8:101::42'] Please fix the error(s) and try again."
|
||||
self.assertEquals(bmsg, form._errors['blacklist'][0]) # pylint: disable=protected-access
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, "The IPFilter could not be created because the data didn't validate."):
|
||||
form.save()
|
||||
159
common/djangoapps/embargo/tests/test_middleware.py
Normal file
159
common/djangoapps/embargo/tests/test_middleware.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Tests for EmbargoMiddleware
|
||||
"""
|
||||
|
||||
import mock
|
||||
import pygeoip
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, Client
|
||||
from django.test.utils import override_settings
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
# Explicitly import the cache from ConfigurationModel so we can reset it after each test
|
||||
from config_models.models import cache
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class EmbargoMiddlewareTests(TestCase):
|
||||
"""
|
||||
Tests of EmbargoMiddleware
|
||||
"""
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = UserFactory(username='fred', password='secret')
|
||||
self.client.login(username='fred', password='secret')
|
||||
self.embargo_course = CourseFactory.create()
|
||||
self.embargo_course.save()
|
||||
self.regular_course = CourseFactory.create(org="Regular")
|
||||
self.regular_course.save()
|
||||
self.embargoed_page = '/courses/' + self.embargo_course.id + '/info'
|
||||
self.regular_page = '/courses/' + self.regular_course.id + '/info'
|
||||
EmbargoedCourse(course_id=self.embargo_course.id, embargoed=True).save()
|
||||
EmbargoedState(
|
||||
embargoed_countries="cu, ir, Sy, SD",
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
CourseEnrollment.enroll(self.user, self.regular_course.id)
|
||||
CourseEnrollment.enroll(self.user, self.embargo_course.id)
|
||||
# Text from lms/templates/static_templates/embargo.html
|
||||
self.embargo_text = "Unfortunately, at this time edX must comply with export controls, and we cannot allow you to access this particular course."
|
||||
|
||||
self.patcher = mock.patch.object(pygeoip.GeoIP, 'country_code_by_addr', self.mock_country_code_by_addr)
|
||||
self.patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
# Explicitly clear ConfigurationModel's cache so tests have a clear cache
|
||||
# and don't interfere with each other
|
||||
cache.clear()
|
||||
self.patcher.stop()
|
||||
|
||||
def mock_country_code_by_addr(self, ip_addr):
|
||||
"""
|
||||
Gives us a fake set of IPs
|
||||
"""
|
||||
ip_dict = {
|
||||
'1.0.0.0': 'CU',
|
||||
'2.0.0.0': 'IR',
|
||||
'3.0.0.0': 'SY',
|
||||
'4.0.0.0': 'SD',
|
||||
'5.0.0.0': 'AQ', # Antartica
|
||||
}
|
||||
return ip_dict.get(ip_addr, 'US')
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_countries(self):
|
||||
# Accessing an embargoed page from a blocked IP should cause a redirect
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
# Following the redirect should give us the embargo page
|
||||
response = self.client.get(
|
||||
self.embargoed_page,
|
||||
HTTP_X_FORWARDED_FOR='1.0.0.0',
|
||||
REMOTE_ADDR='1.0.0.0',
|
||||
follow=True
|
||||
)
|
||||
self.assertIn(self.embargo_text, response.content)
|
||||
|
||||
# Accessing a regular page from a blocked IP should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing an embargoed page from a non-embargoed IP should succeed
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing a regular page from a non-embargoed IP should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_ip_exceptions(self):
|
||||
# Explicitly whitelist/blacklist some IPs
|
||||
IPFilter(
|
||||
whitelist='1.0.0.0',
|
||||
blacklist='5.0.0.0',
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
# Accessing an embargoed page from a blocked IP that's been whitelisted
|
||||
# should succeed
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing a regular course from a blocked IP that's been whitelisted should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing an embargoed course from non-embargoed IP that's been blacklisted
|
||||
# should cause a redirect
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
# Following the redirect should give us the embargo page
|
||||
response = self.client.get(
|
||||
self.embargoed_page,
|
||||
HTTP_X_FORWARDED_FOR='5.0.0.0',
|
||||
REMOTE_ADDR='1.0.0.0',
|
||||
follow=True
|
||||
)
|
||||
self.assertIn(self.embargo_text, response.content)
|
||||
|
||||
# Accessing a regular course from a non-embargoed IP that's been blacklisted should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@mock.patch.dict(settings.FEATURES, {'EMBARGO': False})
|
||||
def test_countries_embargo_off(self):
|
||||
# When the middleware is turned off, all requests should go through
|
||||
# Accessing an embargoed page from a blocked IP OK
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing a regular page from a blocked IP should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='1.0.0.0', REMOTE_ADDR='1.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Explicitly whitelist/blacklist some IPs
|
||||
IPFilter(
|
||||
whitelist='1.0.0.0',
|
||||
blacklist='5.0.0.0',
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
# Accessing an embargoed course from non-embargoed IP that's been blacklisted
|
||||
# should be OK
|
||||
response = self.client.get(self.embargoed_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Accessing a regular course from a non-embargoed IP that's been blacklisted should succeed
|
||||
response = self.client.get(self.regular_page, HTTP_X_FORWARDED_FOR='5.0.0.0', REMOTE_ADDR='5.0.0.0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
80
common/djangoapps/embargo/tests/test_models.py
Normal file
80
common/djangoapps/embargo/tests/test_models.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Test of models for embargo middleware app"""
|
||||
from django.test import TestCase
|
||||
|
||||
from embargo.models import EmbargoedCourse, EmbargoedState, IPFilter
|
||||
|
||||
|
||||
class EmbargoModelsTest(TestCase):
|
||||
"""Test each of the 3 models in embargo.models"""
|
||||
def test_course_embargo(self):
|
||||
course_id = 'abc/123/doremi'
|
||||
# Test that course is not authorized by default
|
||||
self.assertFalse(EmbargoedCourse.is_embargoed(course_id))
|
||||
|
||||
# Authorize
|
||||
cauth = EmbargoedCourse(course_id=course_id, embargoed=True)
|
||||
cauth.save()
|
||||
|
||||
# Now, course should be embargoed
|
||||
self.assertTrue(EmbargoedCourse.is_embargoed(course_id))
|
||||
self.assertEquals(
|
||||
cauth.__unicode__(),
|
||||
"Course 'abc/123/doremi' is Embargoed"
|
||||
)
|
||||
|
||||
# Unauthorize by explicitly setting email_enabled to False
|
||||
cauth.embargoed = False
|
||||
cauth.save()
|
||||
# Test that course is now unauthorized
|
||||
self.assertFalse(EmbargoedCourse.is_embargoed(course_id))
|
||||
self.assertEquals(
|
||||
cauth.__unicode__(),
|
||||
"Course 'abc/123/doremi' is Not Embargoed"
|
||||
)
|
||||
|
||||
def test_state_embargo(self):
|
||||
# Azerbaijan and France should not be blocked
|
||||
good_states = ['AZ', 'FR']
|
||||
# Gah block USA and Antartica
|
||||
blocked_states = ['US', 'AQ']
|
||||
currently_blocked = EmbargoedState.current().embargoed_countries_list
|
||||
|
||||
for state in blocked_states + good_states:
|
||||
self.assertFalse(state in currently_blocked)
|
||||
|
||||
# Block
|
||||
cauth = EmbargoedState(embargoed_countries='US, AQ')
|
||||
cauth.save()
|
||||
currently_blocked = EmbargoedState.current().embargoed_countries_list
|
||||
|
||||
for state in good_states:
|
||||
self.assertFalse(state in currently_blocked)
|
||||
for state in blocked_states:
|
||||
self.assertTrue(state in currently_blocked)
|
||||
|
||||
# Change embargo - block Isle of Man too
|
||||
blocked_states.append('IM')
|
||||
cauth.embargoed_countries = 'US, AQ, IM'
|
||||
cauth.save()
|
||||
currently_blocked = EmbargoedState.current().embargoed_countries_list
|
||||
|
||||
for state in good_states:
|
||||
self.assertFalse(state in currently_blocked)
|
||||
for state in blocked_states:
|
||||
self.assertTrue(state in currently_blocked)
|
||||
|
||||
def test_ip_blocking(self):
|
||||
whitelist = '127.0.0.1'
|
||||
blacklist = '18.244.51.3'
|
||||
|
||||
cwhitelist = IPFilter.current().whitelist_ips
|
||||
self.assertFalse(whitelist in cwhitelist)
|
||||
cblacklist = IPFilter.current().blacklist_ips
|
||||
self.assertFalse(blacklist in cblacklist)
|
||||
|
||||
IPFilter(whitelist=whitelist, blacklist=blacklist).save()
|
||||
|
||||
cwhitelist = IPFilter.current().whitelist_ips
|
||||
self.assertTrue(whitelist in cwhitelist)
|
||||
cblacklist = IPFilter.current().blacklist_ips
|
||||
self.assertTrue(blacklist in cblacklist)
|
||||
@@ -142,6 +142,15 @@ def _get_date_for_press(publish_date):
|
||||
return date
|
||||
|
||||
|
||||
def embargo(_request):
|
||||
"""
|
||||
Render the embargo page.
|
||||
|
||||
Explains to the user why they are not able to access a particular embargoed course.
|
||||
"""
|
||||
return render_to_response('static_templates/embargo.html')
|
||||
|
||||
|
||||
def press(request):
|
||||
json_articles = cache.get("student_press_json_articles")
|
||||
if json_articles is None:
|
||||
@@ -723,7 +732,7 @@ def login_user(request, error=""):
|
||||
# This is actually the common case, logging in user without external linked login
|
||||
AUDIT_LOG.info("User %s w/o external auth attempting login", user)
|
||||
|
||||
# see if account has been locked out due to excessive login failres
|
||||
# see if account has been locked out due to excessive login failures
|
||||
user_found_by_email_lookup = user
|
||||
if user_found_by_email_lookup and LoginFailures.is_feature_enabled():
|
||||
if LoginFailures.is_user_locked_out(user_found_by_email_lookup):
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
"""Generates common contexts"""
|
||||
|
||||
import re
|
||||
import logging
|
||||
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from util.request import COURSE_REGEX
|
||||
|
||||
|
||||
COURSE_REGEX = re.compile(r'^.*?/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)')
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
""" Utility functions related to HTTP requests """
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from microsite_configuration.middleware import MicrositeConfiguration
|
||||
|
||||
COURSE_REGEX = re.compile(r'^.*?/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)')
|
||||
|
||||
|
||||
def safe_get_host(request):
|
||||
"""
|
||||
@@ -16,3 +20,17 @@ def safe_get_host(request):
|
||||
return request.get_host()
|
||||
else:
|
||||
return MicrositeConfiguration.get_microsite_configuration_value('site_domain', settings.SITE_NAME)
|
||||
|
||||
|
||||
def course_id_from_url(url):
|
||||
"""
|
||||
Extracts the course_id from the given `url`.
|
||||
"""
|
||||
url = url or ''
|
||||
|
||||
match = COURSE_REGEX.match(url)
|
||||
course_id = ''
|
||||
if match:
|
||||
course_id = match.group('course_id') or ''
|
||||
|
||||
return course_id
|
||||
|
||||
@@ -255,7 +255,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
count_graded = self.student_data_for_location['count_graded']
|
||||
count_required = self.student_data_for_location['count_required']
|
||||
except:
|
||||
success, response = self.query_data_for_location(self.location)
|
||||
success, response = self.query_data_for_location(self.link_to_location)
|
||||
if not success:
|
||||
log.exception(
|
||||
"No instance data found and could not get data from controller for loc {0} student {1}".format(
|
||||
@@ -706,6 +706,7 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
|
||||
closed = module_attr('closed')
|
||||
get_instance_state = module_attr('get_instance_state')
|
||||
get_next_submission = module_attr('get_next_submission')
|
||||
graded = module_attr('graded')
|
||||
is_student_calibrated = module_attr('is_student_calibrated')
|
||||
peer_grading = module_attr('peer_grading')
|
||||
peer_grading_closed = module_attr('peer_grading_closed')
|
||||
@@ -715,4 +716,6 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
|
||||
save_calibration_essay = module_attr('save_calibration_essay')
|
||||
save_grade = module_attr('save_grade')
|
||||
show_calibration_essay = module_attr('show_calibration_essay')
|
||||
use_for_single_location_local = module_attr('use_for_single_location_local')
|
||||
_find_corresponding_module_for_location = module_attr('_find_corresponding_module_for_location')
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
from mock import Mock
|
||||
from mock import Mock, patch
|
||||
from webob.multidict import MultiDict
|
||||
|
||||
from xblock.field_data import DictFieldData
|
||||
@@ -78,12 +78,13 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
|
||||
success, _data = self.peer_grading.query_data_for_location(self.problem_location.url())
|
||||
self.assertTrue(success)
|
||||
|
||||
def test_get_score(self):
|
||||
def test_get_score_none(self):
|
||||
"""
|
||||
Test getting the score
|
||||
@return:
|
||||
Test getting the score.
|
||||
"""
|
||||
score = self.peer_grading.get_score()
|
||||
|
||||
# Score should be None.
|
||||
self.assertIsNone(score['score'])
|
||||
|
||||
def test_get_max_score(self):
|
||||
@@ -179,6 +180,56 @@ class PeerGradingModuleTest(unittest.TestCase, DummyModulestore):
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_score_success_fails(self):
|
||||
"""
|
||||
Test if query_data_for_location not succeed, their score is None.
|
||||
"""
|
||||
score_dict = self.get_score(False, 0, 0)
|
||||
|
||||
# Score dict should be None.
|
||||
self.assertIsNone(score_dict)
|
||||
|
||||
def test_get_score(self):
|
||||
"""
|
||||
Test if the student has graded equal to required submissions,
|
||||
their score is 1.0.
|
||||
"""
|
||||
|
||||
score_dict = self.get_score(True, 3, 3)
|
||||
|
||||
# Score should be 1.0.
|
||||
self.assertEqual(score_dict["score"], 1.0)
|
||||
|
||||
# Testing score after data is stored in student_data_for_location in xmodule.
|
||||
_score_dict = self.peer_grading.get_score()
|
||||
|
||||
# Score should be 1.0.
|
||||
self.assertEqual(_score_dict["score"], 1.0)
|
||||
|
||||
def test_get_score_zero(self):
|
||||
"""
|
||||
Test if the student has graded not equal to required submissions,
|
||||
their score is 0.0.
|
||||
"""
|
||||
score_dict = self.get_score(True, 2, 3)
|
||||
|
||||
# Score should be 0.0.
|
||||
self.assertEqual(score_dict["score"], 0.0)
|
||||
|
||||
def get_score(self, success, count_graded, count_required):
|
||||
self.peer_grading.use_for_single_location_local = True
|
||||
self.peer_grading.graded = True
|
||||
|
||||
# Patch for external grading service.
|
||||
with patch('xmodule.peer_grading_module.PeerGradingModule.query_data_for_location') as mock_query_data_for_location:
|
||||
mock_query_data_for_location.return_value = (
|
||||
success,
|
||||
{"count_graded": count_graded, "count_required": count_required}
|
||||
)
|
||||
|
||||
# Returning score dict.
|
||||
return self.peer_grading.get_score()
|
||||
|
||||
|
||||
class MockPeerGradingServiceProblemList(MockPeerGradingService):
|
||||
def get_problem_list(self, course_id, grader_id):
|
||||
|
||||
@@ -245,24 +245,28 @@ class VideoModule(VideoFields, XModule):
|
||||
elif self.sub:
|
||||
track_url = self.runtime.handler_url(self, 'transcript').rstrip('/?') + '/download'
|
||||
|
||||
if self.transcript_language in self.transcripts:
|
||||
transcript_language = self.transcript_language
|
||||
elif self.sub:
|
||||
if not self.transcripts:
|
||||
transcript_language = 'en'
|
||||
elif self.transcripts:
|
||||
transcript_language = self.transcripts.keys()[0]
|
||||
languages = {'en': 'English'}
|
||||
else:
|
||||
# this for the case, when for currently selected video,
|
||||
# there are no translations and English subtitles are not set by instructor.
|
||||
transcript_language = 'null'
|
||||
if self.transcript_language in self.transcripts:
|
||||
transcript_language = self.transcript_language
|
||||
elif self.sub:
|
||||
transcript_language = 'en'
|
||||
else:
|
||||
transcript_language = sorted(self.transcripts.keys())[0]
|
||||
|
||||
all_languages = {i[0]: i[1] for i in settings.ALL_LANGUAGES}
|
||||
languages = {lang: all_languages[lang] for lang in self.transcripts}
|
||||
if self.sub:
|
||||
languages.update({'en': 'English'})
|
||||
languages = {
|
||||
lang: display
|
||||
for lang, display in settings.ALL_LANGUAGES
|
||||
if lang in self.transcripts
|
||||
}
|
||||
|
||||
if self.sub:
|
||||
languages['en'] = 'English'
|
||||
|
||||
# OrderedDict for easy testing of rendered context in tests
|
||||
transcript_languages = OrderedDict(sorted(languages.items(), key=itemgetter(1)))
|
||||
sorted_languages = OrderedDict(sorted(languages.items(), key=itemgetter(1)))
|
||||
|
||||
return self.system.render_template('video.html', {
|
||||
'ajax_url': self.system.ajax_url + '/save_user_state',
|
||||
@@ -287,7 +291,7 @@ class VideoModule(VideoFields, XModule):
|
||||
'yt_test_timeout': 1500,
|
||||
'yt_test_url': settings.YOUTUBE_TEST_URL,
|
||||
'transcript_language': transcript_language,
|
||||
'transcript_languages': json.dumps(transcript_languages),
|
||||
'transcript_languages': json.dumps(sorted_languages),
|
||||
'transcript_translation_url': self.runtime.handler_url(self, 'transcript').rstrip('/?') + '/translation',
|
||||
'transcript_available_translations_url': self.runtime.handler_url(self, 'transcript').rstrip('/?') + '/available_translations',
|
||||
})
|
||||
|
||||
BIN
common/static/data/geoip/GeoIP.dat
Normal file
BIN
common/static/data/geoip/GeoIP.dat
Normal file
Binary file not shown.
2
common/static/data/geoip/README
Normal file
2
common/static/data/geoip/README
Normal file
@@ -0,0 +1,2 @@
|
||||
This product includes GeoLite data created by MaxMind, available from
|
||||
http://www.maxmind.com.
|
||||
File diff suppressed because one or more lines are too long
@@ -46,7 +46,7 @@ CREATE TABLE `auth_permission` (
|
||||
UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
|
||||
KEY `auth_permission_e4470c6e` (`content_type_id`),
|
||||
CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=238 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=259 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `auth_registration`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -122,12 +122,14 @@ CREATE TABLE `auth_userprofile` (
|
||||
`location` varchar(255) NOT NULL,
|
||||
`meta` longtext NOT NULL,
|
||||
`courseware` varchar(255) NOT NULL,
|
||||
`gender` varchar(6) DEFAULT NULL,
|
||||
`gender` varchar(6),
|
||||
`mailing_address` longtext,
|
||||
`year_of_birth` int(11) DEFAULT NULL,
|
||||
`level_of_education` varchar(6) DEFAULT NULL,
|
||||
`year_of_birth` int(11),
|
||||
`level_of_education` varchar(6),
|
||||
`goals` longtext,
|
||||
`allow_certificate` tinyint(1) NOT NULL,
|
||||
`country` varchar(2),
|
||||
`city` longtext,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `user_id` (`user_id`),
|
||||
KEY `auth_userprofile_52094d6e` (`name`),
|
||||
@@ -147,6 +149,7 @@ CREATE TABLE `bulk_email_courseauthorization` (
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
`email_enabled` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `bulk_email_courseauthorization_course_id_4f6cee675bf93275_uniq` (`course_id`),
|
||||
KEY `bulk_email_courseauthorization_ff48d8e5` (`course_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
@@ -187,7 +190,7 @@ DROP TABLE IF EXISTS `bulk_email_optout`;
|
||||
CREATE TABLE `bulk_email_optout` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
`user_id` int(11) DEFAULT NULL,
|
||||
`user_id` int(11),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `bulk_email_optout_course_id_368f7519b2997e1a_uniq` (`course_id`,`user_id`),
|
||||
KEY `bulk_email_optout_ff48d8e5` (`course_id`),
|
||||
@@ -366,7 +369,7 @@ CREATE TABLE `courseware_studentmodule` (
|
||||
`grade` double DEFAULT NULL,
|
||||
`created` datetime NOT NULL,
|
||||
`modified` datetime NOT NULL,
|
||||
`max_grade` double DEFAULT NULL,
|
||||
`max_grade` double,
|
||||
`done` varchar(8) NOT NULL,
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
@@ -388,7 +391,7 @@ DROP TABLE IF EXISTS `courseware_studentmodulehistory`;
|
||||
CREATE TABLE `courseware_studentmodulehistory` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`student_module_id` int(11) NOT NULL,
|
||||
`version` varchar(255) DEFAULT NULL,
|
||||
`version` varchar(255),
|
||||
`created` datetime NOT NULL,
|
||||
`state` longtext,
|
||||
`grade` double DEFAULT NULL,
|
||||
@@ -458,6 +461,20 @@ CREATE TABLE `courseware_xmoduleuserstatesummaryfield` (
|
||||
KEY `courseware_xmodulecontentfield_5436e97a` (`modified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `dark_lang_darklangconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `dark_lang_darklangconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`released_languages` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `dark_lang_darklangconfig_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_3fb19c355c5fe834` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_admin_log`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -536,7 +553,7 @@ CREATE TABLE `django_content_type` (
|
||||
`model` varchar(100) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `app_label` (`app_label`,`model`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `django_openid_auth_association`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -644,8 +661,8 @@ CREATE TABLE `djcelery_periodictask` (
|
||||
UNIQUE KEY `name` (`name`),
|
||||
KEY `djcelery_periodictask_17d2d99d` (`interval_id`),
|
||||
KEY `djcelery_periodictask_7aa5fda` (`crontab_id`),
|
||||
CONSTRAINT `interval_id_refs_id_f2054349` FOREIGN KEY (`interval_id`) REFERENCES `djcelery_intervalschedule` (`id`),
|
||||
CONSTRAINT `crontab_id_refs_id_ebff5e74` FOREIGN KEY (`crontab_id`) REFERENCES `djcelery_crontabschedule` (`id`)
|
||||
CONSTRAINT `crontab_id_refs_id_ebff5e74` FOREIGN KEY (`crontab_id`) REFERENCES `djcelery_crontabschedule` (`id`),
|
||||
CONSTRAINT `interval_id_refs_id_f2054349` FOREIGN KEY (`interval_id`) REFERENCES `djcelery_intervalschedule` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `djcelery_periodictasks`;
|
||||
@@ -698,6 +715,46 @@ CREATE TABLE `djcelery_workerstate` (
|
||||
KEY `djcelery_workerstate_eb8ac7e4` (`last_heartbeat`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `embargo_embargoedcourse`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `embargo_embargoedcourse` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
`embargoed` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `course_id` (`course_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `embargo_embargoedstate`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `embargo_embargoedstate` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`embargoed_countries` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `embargo_embargoedstate_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_3c8b83add0205d39` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `embargo_ipfilter`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `embargo_ipfilter` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`whitelist` longtext NOT NULL,
|
||||
`blacklist` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `embargo_ipfilter_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_3babbf0a22c1f5d3` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `external_auth_externalauthmap`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -933,6 +990,18 @@ CREATE TABLE `psychometrics_psychometricdata` (
|
||||
UNIQUE KEY `studentmodule_id` (`studentmodule_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `reverification_midcoursereverificationwindow`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `reverification_midcoursereverificationwindow` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`course_id` varchar(255) NOT NULL,
|
||||
`start_date` datetime DEFAULT NULL,
|
||||
`end_date` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `reverification_midcoursereverificationwindow_ff48d8e5` (`course_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `shoppingcart_certificateitem`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -969,7 +1038,7 @@ CREATE TABLE `shoppingcart_order` (
|
||||
`bill_to_ccnum` varchar(8) NOT NULL,
|
||||
`bill_to_cardtype` varchar(32) NOT NULL,
|
||||
`processor_reply_dump` longtext NOT NULL,
|
||||
`refunded_time` datetime DEFAULT NULL,
|
||||
`refunded_time` datetime,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `shoppingcart_order_fbfc09f1` (`user_id`),
|
||||
CONSTRAINT `user_id_refs_id_a4b0342e1195673` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
@@ -987,9 +1056,9 @@ CREATE TABLE `shoppingcart_orderitem` (
|
||||
`unit_cost` decimal(30,2) NOT NULL,
|
||||
`line_desc` varchar(1024) NOT NULL,
|
||||
`currency` varchar(8) NOT NULL,
|
||||
`fulfilled_time` datetime DEFAULT NULL,
|
||||
`fulfilled_time` datetime,
|
||||
`report_comments` longtext NOT NULL,
|
||||
`refund_requested_time` datetime DEFAULT NULL,
|
||||
`refund_requested_time` datetime,
|
||||
`service_fee` decimal(30,2) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `shoppingcart_orderitem_8337030b` (`order_id`),
|
||||
@@ -1034,7 +1103,25 @@ CREATE TABLE `south_migrationhistory` (
|
||||
`migration` varchar(255) NOT NULL,
|
||||
`applied` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=114 DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=125 DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `splash_splashconfig`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `splash_splashconfig` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`change_date` datetime NOT NULL,
|
||||
`changed_by_id` int(11) DEFAULT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
`cookie_name` longtext NOT NULL,
|
||||
`cookie_allowed_values` longtext NOT NULL,
|
||||
`unaffected_usernames` longtext NOT NULL,
|
||||
`redirect_url` varchar(200) NOT NULL,
|
||||
`unaffected_url_paths` longtext NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `splash_splashconfig_16905482` (`changed_by_id`),
|
||||
CONSTRAINT `changed_by_id_refs_id_6024c0b79125b21c` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `student_anonymoususerid`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
@@ -1085,6 +1172,19 @@ CREATE TABLE `student_courseenrollmentallowed` (
|
||||
KEY `student_courseenrollmentallowed_3216ff68` (`created`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `student_loginfailures`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `student_loginfailures` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`failure_count` int(11) NOT NULL,
|
||||
`lockout_until` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `student_loginfailures_fbfc09f1` (`user_id`),
|
||||
CONSTRAINT `user_id_refs_id_50dcb1c1e6a71045` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `student_pendingemailchange`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
@@ -1167,7 +1267,7 @@ CREATE TABLE `track_trackinglog` (
|
||||
`event_type` varchar(512) NOT NULL,
|
||||
`event` longtext NOT NULL,
|
||||
`agent` varchar(256) NOT NULL,
|
||||
`page` varchar(512) DEFAULT NULL,
|
||||
`page` varchar(512),
|
||||
`time` datetime NOT NULL,
|
||||
`host` varchar(64) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@@ -1208,6 +1308,8 @@ CREATE TABLE `verify_student_softwaresecurephotoverification` (
|
||||
`error_msg` longtext NOT NULL,
|
||||
`error_code` varchar(50) NOT NULL,
|
||||
`photo_id_key` longtext NOT NULL,
|
||||
`window_id` int(11),
|
||||
`display` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `verify_student_softwaresecurephotoverification_fbfc09f1` (`user_id`),
|
||||
KEY `verify_student_softwaresecurephotoverification_8713c555` (`receipt_id`),
|
||||
@@ -1215,8 +1317,11 @@ CREATE TABLE `verify_student_softwaresecurephotoverification` (
|
||||
KEY `verify_student_softwaresecurephotoverification_f84f7de6` (`updated_at`),
|
||||
KEY `verify_student_softwaresecurephotoverification_4452d192` (`submitted_at`),
|
||||
KEY `verify_student_softwaresecurephotoverification_b2c165b4` (`reviewing_user_id`),
|
||||
KEY `verify_student_softwaresecurephotoverification_7343ffda` (`window_id`),
|
||||
KEY `verify_student_softwaresecurephotoverification_35eebcb6` (`display`),
|
||||
CONSTRAINT `reviewing_user_id_refs_id_5b90d52ad6ea4207` FOREIGN KEY (`reviewing_user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `user_id_refs_id_5b90d52ad6ea4207` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
|
||||
CONSTRAINT `user_id_refs_id_5b90d52ad6ea4207` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`),
|
||||
CONSTRAINT `window_id_refs_id_30f70c30fce8f38a` FOREIGN KEY (`window_id`) REFERENCES `reverification_midcoursereverificationwindow` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `waffle_flag`;
|
||||
@@ -1448,9 +1553,9 @@ DROP TABLE IF EXISTS `wiki_imagerevision`;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `wiki_imagerevision` (
|
||||
`revisionpluginrevision_ptr_id` int(11) NOT NULL,
|
||||
`image` varchar(2000) DEFAULT NULL,
|
||||
`width` smallint(6) DEFAULT NULL,
|
||||
`height` smallint(6) DEFAULT NULL,
|
||||
`image` varchar(2000),
|
||||
`width` smallint(6),
|
||||
`height` smallint(6),
|
||||
PRIMARY KEY (`revisionpluginrevision_ptr_id`),
|
||||
CONSTRAINT `revisionpluginrevision_ptr_id_refs_id_5da3ee545b9fc791` FOREIGN KEY (`revisionpluginrevision_ptr_id`) REFERENCES `wiki_revisionpluginrevision` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -1484,7 +1589,7 @@ DROP TABLE IF EXISTS `wiki_revisionplugin`;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `wiki_revisionplugin` (
|
||||
`articleplugin_ptr_id` int(11) NOT NULL,
|
||||
`current_revision_id` int(11) DEFAULT NULL,
|
||||
`current_revision_id` int(11),
|
||||
PRIMARY KEY (`articleplugin_ptr_id`),
|
||||
UNIQUE KEY `current_revision_id` (`current_revision_id`),
|
||||
CONSTRAINT `current_revision_id_refs_id_2732d4b244938e26` FOREIGN KEY (`current_revision_id`) REFERENCES `wiki_revisionpluginrevision` (`id`),
|
||||
|
||||
@@ -208,8 +208,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
context = self.item_descriptor.render('student_view').content
|
||||
|
||||
expected_context.update({
|
||||
'transcript_languages': '{"en": "English"}' if self.item_descriptor.sub else '{}',
|
||||
'transcript_language': 'en' if self.item_descriptor.sub else json.dumps(None),
|
||||
'transcript_languages': '{"en": "English"}',
|
||||
'transcript_language': 'en',
|
||||
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
self.item_descriptor, 'transcript'
|
||||
).rstrip('/?') + '/translation',
|
||||
|
||||
@@ -221,6 +221,9 @@ FEATURES = {
|
||||
|
||||
# Hide any Personally Identifiable Information from application logs
|
||||
'SQUELCH_PII_IN_LOGS': False,
|
||||
|
||||
# Toggle embargo functionality
|
||||
'EMBARGO': False,
|
||||
}
|
||||
|
||||
# Used for A/B testing
|
||||
@@ -259,6 +262,9 @@ node_paths = [
|
||||
]
|
||||
NODE_PATH = ':'.join(node_paths)
|
||||
|
||||
# For geolocation ip database
|
||||
GEOIP_PATH = REPO_ROOT / "common/static/data/geoip/GeoIP.dat"
|
||||
|
||||
|
||||
# Where to look for a status message
|
||||
STATUS_MESSAGE_PATH = ENV_ROOT / "status_message.json"
|
||||
@@ -703,6 +709,7 @@ MIDDLEWARE_CLASSES = (
|
||||
|
||||
# Allows us to dark-launch particular languages
|
||||
'dark_lang.middleware.DarkLangMiddleware',
|
||||
'embargo.middleware.EmbargoMiddleware',
|
||||
|
||||
# Allows us to set user preferences
|
||||
# should be after DarkLangMiddleware
|
||||
@@ -727,6 +734,7 @@ MIDDLEWARE_CLASSES = (
|
||||
|
||||
# for expiring inactive sessions
|
||||
'session_inactivity_timeout.middleware.SessionInactivityTimeout',
|
||||
|
||||
)
|
||||
|
||||
############################### Pipeline #######################################
|
||||
@@ -1141,6 +1149,8 @@ INSTALLED_APPS = (
|
||||
|
||||
# Student Identity Reverification
|
||||
'reverification',
|
||||
|
||||
'embargo',
|
||||
)
|
||||
|
||||
######################### MARKETING SITE ###############################
|
||||
|
||||
@@ -40,6 +40,9 @@ FEATURES['ENABLE_SHOPPING_CART'] = True
|
||||
FEATURES['ENABLE_S3_GRADE_DOWNLOADS'] = True
|
||||
FEATURES['ALLOW_COURSE_STAFF_GRADE_DOWNLOADS'] = True
|
||||
|
||||
# Toggles embargo on for testing
|
||||
FEATURES['EMBARGO'] = True
|
||||
|
||||
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
|
||||
WIKI_ENABLED = True
|
||||
|
||||
|
||||
8
lms/templates/static_templates/embargo.html
Normal file
8
lms/templates/static_templates/embargo.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="../main.html" />
|
||||
|
||||
<%block name="pagetitle">${_("This Course Unavailable In Your Country")}</%block>
|
||||
|
||||
<section class="outside-app">
|
||||
<p>${_("Our system indicates that you are trying to access an edX course from an IP address associated with a country currently subjected to U.S. economic and trade sanctions. Unfortunately, at this time edX must comply with export controls, and we cannot allow you to access this particular course. Feel free to browse our catalogue to find other courses you may be interested in taking.")}</p>
|
||||
</section>
|
||||
@@ -11,7 +11,6 @@ if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
|
||||
|
||||
urlpatterns = ('', # nopep8
|
||||
# certificate view
|
||||
|
||||
url(r'^update_certificate$', 'certificates.views.update_certificate'),
|
||||
url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware
|
||||
url(r'^dashboard$', 'student.views.dashboard', name="dashboard"),
|
||||
@@ -66,6 +65,8 @@ urlpatterns = ('', # nopep8
|
||||
url(r'^', include('waffle.urls')),
|
||||
|
||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||
|
||||
url(r'^embargo$', 'student.views.embargo', name="embargo"),
|
||||
)
|
||||
|
||||
# if settings.FEATURES.get("MULTIPLE_ENROLLMENT_ROLES"):
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
-e git+https://github.com/gabrielfalcao/lettuce.git@cccc3978ad2df82a78b6f9648fe2e9baddd22f88#egg=lettuce
|
||||
-e git+https://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
|
||||
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
|
||||
-e git+https://github.com/un33k/django-ipware.git@42cb1bb1dc680a60c6452e8bb2b843c2a0382c90#egg=django-ipware
|
||||
-e git+https://github.com/appliedsec/pygeoip.git@95e69341cebf5a6a9fbf7c4f5439d458898bdc3b#egg=pygeoip
|
||||
|
||||
# Our libraries:
|
||||
-e git+https://github.com/edx/XBlock.git@893cd83dfb24405ce81b07f49c1c2e3053cdc865#egg=XBlock
|
||||
|
||||
Reference in New Issue
Block a user