Commit 07e56ac4 by Jason Bau

Merge pull request #67 from edx/features/jbau/stanford-shib

Shibboleth Auth
parents 51469ad3 aa4e27f7
# -*- 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 'ExternalAuthMap'
db.create_table('external_auth_externalauthmap', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('external_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('external_domain', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('external_credentials', self.gf('django.db.models.fields.TextField')(blank=True)),
('external_email', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('external_name', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=255, blank=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True, null=True)),
('internal_password', self.gf('django.db.models.fields.CharField')(max_length=31, blank=True)),
('dtcreated', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('dtsignup', self.gf('django.db.models.fields.DateTimeField')(null=True)),
))
db.send_create_signal('external_auth', ['ExternalAuthMap'])
# Adding unique constraint on 'ExternalAuthMap', fields ['external_id', 'external_domain']
db.create_unique('external_auth_externalauthmap', ['external_id', 'external_domain'])
def backwards(self, orm):
# Removing unique constraint on 'ExternalAuthMap', fields ['external_id', 'external_domain']
db.delete_unique('external_auth_externalauthmap', ['external_id', 'external_domain'])
# Deleting model 'ExternalAuthMap'
db.delete_table('external_auth_externalauthmap')
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'})
},
'external_auth.externalauthmap': {
'Meta': {'unique_together': "(('external_id', 'external_domain'),)", 'object_name': 'ExternalAuthMap'},
'dtcreated': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'dtsignup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'external_credentials': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'external_domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'external_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'external_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'external_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'internal_password': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True'})
}
}
complete_apps = ['external_auth']
\ No newline at end of file
...@@ -45,6 +45,8 @@ from collections import namedtuple ...@@ -45,6 +45,8 @@ from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement from courseware.courses import get_courses, sort_by_announcement
from courseware.access import has_access from courseware.access import has_access
from external_auth.models import ExternalAuthMap
from statsd import statsd from statsd import statsd
from pytz import UTC from pytz import UTC
...@@ -226,7 +228,7 @@ def signin_user(request): ...@@ -226,7 +228,7 @@ def signin_user(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def register_user(request): def register_user(request, extra_context={}):
""" """
This view will display the non-modal registration form This view will display the non-modal registration form
""" """
...@@ -237,6 +239,8 @@ def register_user(request): ...@@ -237,6 +239,8 @@ def register_user(request):
'course_id': request.GET.get('course_id'), 'course_id': request.GET.get('course_id'),
'enrollment_action': request.GET.get('enrollment_action') 'enrollment_action': request.GET.get('enrollment_action')
} }
context.update(extra_context)
return render_to_response('register.html', context) return render_to_response('register.html', context)
...@@ -278,9 +282,17 @@ def dashboard(request): ...@@ -278,9 +282,17 @@ def dashboard(request):
# Get the 3 most recent news # Get the 3 most recent news
top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None
# get info w.r.t ExternalAuthMap
external_auth_map = None
try:
external_auth_map = ExternalAuthMap.objects.get(user=user)
except ExternalAuthMap.DoesNotExist:
pass
context = {'courses': courses, context = {'courses': courses,
'message': message, 'message': message,
'external_auth_map': external_auth_map,
'staff_access': staff_access, 'staff_access': staff_access,
'errored_courses': errored_courses, 'errored_courses': errored_courses,
'show_courseware_links_for': show_courseware_links_for, 'show_courseware_links_for': show_courseware_links_for,
...@@ -567,15 +579,23 @@ def create_account(request, post_override=None): ...@@ -567,15 +579,23 @@ def create_account(request, post_override=None):
# if doing signup for an external authorization, then get email, password, name from the eamap # if doing signup for an external authorization, then get email, password, name from the eamap
# don't use the ones from the form, since the user could have hacked those # don't use the ones from the form, since the user could have hacked those
# unless originally we didn't get a valid email or name from the external auth
DoExternalAuth = 'ExternalAuthMap' in request.session DoExternalAuth = 'ExternalAuthMap' in request.session
if DoExternalAuth: if DoExternalAuth:
eamap = request.session['ExternalAuthMap'] eamap = request.session['ExternalAuthMap']
email = eamap.external_email try:
name = eamap.external_name validate_email(eamap.external_email)
email = eamap.external_email
except ValidationError:
email = post_vars.get('email', '')
if eamap.external_name.strip() == '':
name = post_vars.get('name', '')
else:
name = eamap.external_name
password = eamap.internal_password password = eamap.internal_password
post_vars = dict(post_vars.items()) post_vars = dict(post_vars.items())
post_vars.update(dict(email=email, name=name, password=password)) post_vars.update(dict(email=email, name=name, password=password))
log.debug('extauth test: post_vars = %s' % post_vars) log.info('In create_account with external_auth: post_vars = %s' % post_vars)
# Confirm we have a properly formed request # Confirm we have a properly formed request
for a in ['username', 'email', 'password', 'name']: for a in ['username', 'email', 'password', 'name']:
...@@ -589,17 +609,28 @@ def create_account(request, post_override=None): ...@@ -589,17 +609,28 @@ def create_account(request, post_override=None):
js['field'] = 'honor_code' js['field'] = 'honor_code'
return HttpResponse(json.dumps(js)) return HttpResponse(json.dumps(js))
if post_vars.get('terms_of_service', 'false') != u'true': # Can't have terms of service for certain SHIB users, like at Stanford
js['value'] = "You must accept the terms of service.".format(field=a) tos_not_required = settings.MITX_FEATURES.get("AUTH_USE_SHIB") \
js['field'] = 'terms_of_service' and settings.MITX_FEATURES.get('SHIB_DISABLE_TOS') \
return HttpResponse(json.dumps(js)) and DoExternalAuth and ("shib" in eamap.external_domain)
if not tos_not_required:
if post_vars.get('terms_of_service', 'false') != u'true':
js['value'] = "You must accept the terms of service.".format(field=a)
js['field'] = 'terms_of_service'
return HttpResponse(json.dumps(js))
# Confirm appropriate fields are there. # Confirm appropriate fields are there.
# TODO: Check e-mail format is correct. # TODO: Check e-mail format is correct.
# TODO: Confirm e-mail is not from a generic domain (mailinator, etc.)? Not sure if # TODO: Confirm e-mail is not from a generic domain (mailinator, etc.)? Not sure if
# this is a good idea # this is a good idea
# TODO: Check password is sane # TODO: Check password is sane
for a in ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']:
required_post_vars = ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']
if tos_not_required:
required_post_vars = ['username', 'email', 'name', 'password', 'honor_code']
for a in required_post_vars:
if len(post_vars[a]) < 2: if len(post_vars[a]) < 2:
error_str = {'username': 'Username must be minimum of two characters long.', error_str = {'username': 'Username must be minimum of two characters long.',
'email': 'A properly formatted e-mail is required.', 'email': 'A properly formatted e-mail is required.',
...@@ -661,19 +692,20 @@ def create_account(request, post_override=None): ...@@ -661,19 +692,20 @@ def create_account(request, post_override=None):
login(request, login_user) login(request, login_user)
request.session.set_expiry(0) request.session.set_expiry(0)
try_change_enrollment(request)
if DoExternalAuth: if DoExternalAuth:
eamap.user = login_user eamap.user = login_user
eamap.dtsignup = datetime.datetime.now(UTC) eamap.dtsignup = datetime.datetime.now(UTC)
eamap.save() eamap.save()
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'], eamap)) log.info("User registered with external_auth %s" % post_vars['username'])
log.info('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'], eamap))
if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'): if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
log.debug('bypassing activation email') log.info('bypassing activation email')
login_user.is_active = True login_user.is_active = True
login_user.save() login_user.save()
try_change_enrollment(request)
statsd.increment("common.student.account_created") statsd.increment("common.student.account_created")
js = {'success': True} js = {'success': True}
......
...@@ -179,6 +179,8 @@ class CourseFields(object): ...@@ -179,6 +179,8 @@ class CourseFields(object):
checklists = List(scope=Scope.settings) checklists = List(scope=Scope.settings)
info_sidebar_name = String(scope=Scope.settings, default='Course Handouts') info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True) show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
scope=Scope.settings)
# An extra property is used rather than the wiki_slug/number because # An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows # there are courses that change the number for different runs. This allows
......
...@@ -14,6 +14,7 @@ from xmodule.modulestore import Location ...@@ -14,6 +14,7 @@ from xmodule.modulestore import Location
from xmodule.x_module import XModule, XModuleDescriptor from xmodule.x_module import XModule, XModuleDescriptor
from student.models import CourseEnrollmentAllowed from student.models import CourseEnrollmentAllowed
from external_auth.models import ExternalAuthMap
from courseware.masquerade import is_masquerading_as_student from courseware.masquerade import is_masquerading_as_student
from django.utils.timezone import UTC from django.utils.timezone import UTC
...@@ -129,15 +130,33 @@ def _has_access_course_desc(user, course, action): ...@@ -129,15 +130,33 @@ def _has_access_course_desc(user, course, action):
def can_enroll(): def can_enroll():
""" """
If the course has an enrollment period, check whether we are in it. First check if restriction of enrollment by login method is enabled, both
globally and by the course.
If it is, then the user must pass the criterion set by the course, e.g. that ExternalAuthMap
was set by 'shib:https://idp.stanford.edu/", in addition to requirements below.
Rest of requirements:
Enrollment can only happen in the course enrollment period, if one exists.
or
(CourseEnrollmentAllowed always overrides)
(staff can always enroll) (staff can always enroll)
""" """
# if using registration method to restrict (say shibboleth)
if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
if user is not None and user.is_authenticated() and \
ExternalAuthMap.objects.filter(user=user, external_domain=course.enrollment_domain):
debug("Allow: external_auth of " + course.enrollment_domain)
reg_method_ok = True
else:
reg_method_ok = False
else:
reg_method_ok = True #if not using this access check, it's always OK.
now = datetime.now(UTC()) now = datetime.now(UTC())
start = course.enrollment_start start = course.enrollment_start
end = course.enrollment_end end = course.enrollment_end
if (start is None or now > start) and (end is None or now < end): if reg_method_ok and (start is None or now > start) and (end is None or now < end):
# in enrollment period, so any user is allowed to enroll. # in enrollment period, so any user is allowed to enroll.
debug("Allow: in enrollment period") debug("Allow: in enrollment period")
return True return True
......
...@@ -81,7 +81,7 @@ class AccessTestCase(TestCase): ...@@ -81,7 +81,7 @@ class AccessTestCase(TestCase):
u = Mock() u = Mock()
yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1) yesterday = datetime.datetime.now(UTC()) - datetime.timedelta(days=1)
tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1) tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow) c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow, enrollment_domain='')
# User can enroll if it is between the start and end dates # User can enroll if it is between the start and end dates
self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
...@@ -91,7 +91,7 @@ class AccessTestCase(TestCase): ...@@ -91,7 +91,7 @@ class AccessTestCase(TestCase):
u = Mock(email='test@edx.org', is_staff=False) u = Mock(email='test@edx.org', is_staff=False)
u.is_authenticated.return_value = True u.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/2012_Fall') c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/2012_Fall', enrollment_domain='')
allowed = CourseEnrollmentAllowedFactory(email=u.email, course_id=c.id) allowed = CourseEnrollmentAllowedFactory(email=u.email, course_id=c.id)
...@@ -101,7 +101,7 @@ class AccessTestCase(TestCase): ...@@ -101,7 +101,7 @@ class AccessTestCase(TestCase):
u = Mock(email='test@edx.org', is_staff=True) u = Mock(email='test@edx.org', is_staff=True)
u.is_authenticated.return_value = True u.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/Whenever') c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/Whenever', enrollment_domain='')
self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) self.assertTrue(access._has_access_course_desc(u, c, 'enroll'))
# TODO: # TODO:
......
...@@ -138,6 +138,10 @@ MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {})) ...@@ -138,6 +138,10 @@ MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {}))
#Timezone overrides #Timezone overrides
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE) TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE)
#Additional installed apps
for app in ENV_TOKENS.get('ADDL_INSTALLED_APPS', []):
INSTALLED_APPS += (app,)
for feature, value in ENV_TOKENS.get('MITX_FEATURES', {}).items(): for feature, value in ENV_TOKENS.get('MITX_FEATURES', {}).items():
MITX_FEATURES[feature] = value MITX_FEATURES[feature] = value
......
...@@ -91,6 +91,14 @@ MITX_FEATURES = { ...@@ -91,6 +91,14 @@ MITX_FEATURES = {
'AUTH_USE_OPENID': False, 'AUTH_USE_OPENID': False,
'AUTH_USE_MIT_CERTIFICATES': False, 'AUTH_USE_MIT_CERTIFICATES': False,
'AUTH_USE_OPENID_PROVIDER': False, 'AUTH_USE_OPENID_PROVIDER': False,
'AUTH_USE_SHIB': False,
# This flag disables the requirement of having to agree to the TOS for users registering
# with Shib. Feature was requested by Stanford's office of general counsel
'SHIB_DISABLE_TOS': False,
# Enables ability to restrict enrollment in specific courses by the user account login method
'RESTRICT_ENROLL_BY_REG_METHOD': False,
# analytics experiments # analytics experiments
'ENABLE_INSTRUCTOR_ANALYTICS': False, 'ENABLE_INSTRUCTOR_ANALYTICS': False,
...@@ -699,6 +707,10 @@ INSTALLED_APPS = ( ...@@ -699,6 +707,10 @@ INSTALLED_APPS = (
'licenses', 'licenses',
'course_groups', 'course_groups',
# External auth (OpenID, shib)
'external_auth',
'django_openid_auth',
#For the wiki #For the wiki
'wiki', # The new django-wiki from benjaoming 'wiki', # The new django-wiki from benjaoming
'django_notify', 'django_notify',
......
...@@ -232,6 +232,9 @@ FILE_UPLOAD_HANDLERS = ( ...@@ -232,6 +232,9 @@ FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.TemporaryFileUploadHandler', 'django.core.files.uploadhandler.TemporaryFileUploadHandler',
) )
MITX_FEATURES['AUTH_USE_SHIB'] = True
MITX_FEATURES['RESTRICT_ENROLL_BY_REG_METHOD'] = True
########################### PIPELINE ################################# ########################### PIPELINE #################################
PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT) PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
......
...@@ -137,14 +137,16 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd' ...@@ -137,14 +137,16 @@ SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
MITX_FEATURES['AUTH_USE_OPENID'] = True MITX_FEATURES['AUTH_USE_OPENID'] = True
MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
################################## SHIB #######################################
MITX_FEATURES['AUTH_USE_SHIB'] = True
MITX_FEATURES['SHIB_DISABLE_TOS'] = True
MITX_FEATURES['RESTRICT_ENROLL_BY_REG_METHOD'] = True
OPENID_CREATE_USERS = False OPENID_CREATE_USERS = False
OPENID_UPDATE_DETAILS_FROM_SREG = True OPENID_UPDATE_DETAILS_FROM_SREG = True
OPENID_USE_AS_ADMIN_LOGIN = False OPENID_USE_AS_ADMIN_LOGIN = False
OPENID_PROVIDER_TRUSTED_ROOTS = ['*'] OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('django_openid_auth',)
################################# CELERY ###################################### ################################# CELERY ######################################
CELERY_ALWAYS_EAGER = True CELERY_ALWAYS_EAGER = True
......
...@@ -24,6 +24,26 @@ ...@@ -24,6 +24,26 @@
event.preventDefault(); event.preventDefault();
}); });
## making the conditional around this entire JS block for sanity
%if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
$('#class_enroll_form').on('ajax:complete', function(event, xhr) {
if(xhr.status == 200) {
location.href = "${reverse('dashboard')}";
} else if (xhr.status == 403) {
location.href = "${reverse('course-specific-register', args=[course.id])}?course_id=${course.id}&enrollment_action=enroll";
} else if (xhr.status == 400) { //This means the user did not have permission
$('#register_error').html('This course has restricted enrollment. Sorry, you do not have permission to enroll.<br />' +
'You may need to log out and re-login with a university account, such as WebAuth'
).css("display", "block");
} else {
$('#register_error').html(
(xhr.responseText ? xhr.responseText : 'An error occurred. Please try again later.')
).css("display", "block");
}
});
%else:
$('#class_enroll_form').on('ajax:complete', function(event, xhr) { $('#class_enroll_form').on('ajax:complete', function(event, xhr) {
if(xhr.status == 200) { if(xhr.status == 200) {
location.href = "${reverse('dashboard')}"; location.href = "${reverse('dashboard')}";
...@@ -35,13 +55,16 @@ ...@@ -35,13 +55,16 @@
).css("display", "block"); ).css("display", "block");
} }
}); });
%endif
})(this) })(this)
</script> </script>
<script src="${static.url('js/course_info.js')}"></script> <script src="${static.url('js/course_info.js')}"></script>
</%block> </%block>
<%block name="title"><title>About ${course.number}</title></%block> <%block name="title"><title>About ${course.number}</title></%block>
<section class="course-info"> <section class="course-info">
...@@ -92,7 +115,7 @@ ...@@ -92,7 +115,7 @@
</div> </div>
</div> </div>
</header> </header>
<section class="container"> <section class="container">
<section class="details"> <section class="details">
<nav> <nav>
......
...@@ -138,8 +138,14 @@ ...@@ -138,8 +138,14 @@
<span class="title"><div class="icon name-icon"></div>Full Name (<a href="#apply_name_change" rel="leanModal" class="edit-name">edit</a>)</span> <span class="data">${ user.profile.name | h }</span> <span class="title"><div class="icon name-icon"></div>Full Name (<a href="#apply_name_change" rel="leanModal" class="edit-name">edit</a>)</span> <span class="data">${ user.profile.name | h }</span>
</li> </li>
<li> <li>
<span class="title"><div class="icon email-icon"></div>Email (<a href="#change_email" rel="leanModal" class="edit-email">edit</a>)</span> <span class="data">${ user.email | h }</span> <span class="title"><div class="icon email-icon"></div>Email
% if external_auth_map is None or 'shib' not in external_auth_map.external_domain:
(<a href="#change_email" rel="leanModal" class="edit-email">edit</a>)
% endif
</span> <span class="data">${ user.email | h }</span>
</li> </li>
% if external_auth_map is None or 'shib' not in external_auth_map.external_domain:
<li> <li>
<span class="title"><a href="#password_reset_complete" rel="leanModal" id="pwd_reset_button">Reset Password</a></span> <span class="title"><a href="#password_reset_complete" rel="leanModal" id="pwd_reset_button">Reset Password</a></span>
<form id="password_reset_form" method="post" data-remote="true" action="${reverse('password_reset')}"> <form id="password_reset_form" method="post" data-remote="true" action="${reverse('password_reset')}">
...@@ -147,6 +153,8 @@ ...@@ -147,6 +153,8 @@
<!-- <input type="submit" id="pwd_reset_button" value="Reset Password" /> --> <!-- <input type="submit" id="pwd_reset_button" value="Reset Password" /> -->
</form> </form>
</li> </li>
% endif
</ul> </ul>
</section> </section>
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
"http://www.w3.org/TR/html4/strict.dtd"> "http://www.w3.org/TR/html4/strict.dtd">
<html> <html>
<head> <head>
<title>OpenID failed</title> <title>External Authentication failed</title>
</head> </head>
<body> <body>
<h1>OpenID failed</h1> <h1>External Authentication failed</h1>
<p>${message}</p> <p>${message}</p>
</body> </body>
</html> </html>
...@@ -95,16 +95,26 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -95,16 +95,26 @@ site_status_msg = get_site_status_msg(course_id)
% endif % endif
</%block> </%block>
% if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']: % if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']:
<li class="nav-global-04"> % if course and settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-register" href="/register">Register Now</a> <li class="nav-global-04">
</li> <a class="cta cta-register" href="${reverse('course-specific-register', args=[course.id])}">Register Now</a>
</li>
% else:
<li class="nav-global-04">
<a class="cta cta-register" href="/register">Register Now</a>
</li>
% endif
% endif % endif
</ol> </ol>
<ol class="right nav-courseware"> <ol class="right nav-courseware">
<li class="nav-courseware-01"> <li class="nav-courseware-01">
% if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']: % if not settings.MITX_FEATURES['DISABLE_LOGIN_BUTTON']:
<a class="cta cta-login" href="/login${login_query()}">Log in</a> % if course and settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-login" href="${reverse('course-specific-login', args=[course.id])}${login_query()}">Log in</a>
% else:
<a class="cta cta-login" href="/login${login_query()}">Log in</a>
% endif
% endif % endif
</li> </li>
</ol> </ol>
......
...@@ -136,16 +136,37 @@ ...@@ -136,16 +136,37 @@
% else: % else:
<div class="message"> <div class="message">
<h3 class="message-title">Welcome ${extauth_email}</h3> <h3 class="message-title">Welcome ${extauth_id}</h3>
<p class="message-copy">Enter a public username:</p> <p class="message-copy">Enter a public username:</p>
</div> </div>
<ol class="list-input"> <ol class="list-input">
% if ask_for_email:
<li class="field required text" id="field-email">
<label for="email">E-mail</label>
<input class="" id="email" type="email" name="email" value="" placeholder="example: username@domain.com" />
</li>
% endif
<li class="field required text" id="field-username"> <li class="field required text" id="field-username">
<label for="username">Public Username</label> <label for="username">Public Username</label>
<input id="username" type="text" name="username" value="${extauth_username}" placeholder="example: JaneDoe" required aria-required="true" /> <input id="username" type="text" name="username" value="${extauth_username}" placeholder="example: JaneDoe" required aria-required="true" />
<span class="tip tip-input">Will be shown in any discussions or forums you participate in</span> <span class="tip tip-input">Will be shown in any discussions or forums you participate in</span>
</li> </li>
% if ask_for_fullname:
<li class="field required text" id="field-name">
<label for="name">Full Name</label>
<input id="name" type="text" name="name" value="" placeholder="example: Jane Doe" />
<span class="tip tip-input">Needed for any certificates you may earn <strong>(cannot be changed later)</strong></span>
</li>
% endif
</ol> </ol>
% endif % endif
...@@ -210,11 +231,16 @@ ...@@ -210,11 +231,16 @@
<ol class="list-input"> <ol class="list-input">
<li class="field-group"> <li class="field-group">
% if has_extauth_info is UNDEFINED or ask_for_tos :
<div class="field required checkbox" id="field-tos"> <div class="field required checkbox" id="field-tos">
<input id="tos-yes" type="checkbox" name="terms_of_service" value="true" required aria-required="true" /> <input id="tos-yes" type="checkbox" name="terms_of_service" value="true" required aria-required="true" />
<label for="tos-yes">I agree to the <a href="${marketing_link('TOS')}" class="new-vp">Terms of Service</a></label> <label for="tos-yes">I agree to the <a href="${marketing_link('TOS')}" class="new-vp">Terms of Service</a></label>
</div> </div>
% endif
<div class="field required checkbox" id="field-honorcode"> <div class="field required checkbox" id="field-honorcode">
<input id="honorcode-yes" type="checkbox" name="honor_code" value="true" /> <input id="honorcode-yes" type="checkbox" name="honor_code" value="true" />
<% <%
...@@ -246,6 +272,8 @@ ...@@ -246,6 +272,8 @@
<h3 class="sr">Registration Help</h3> <h3 class="sr">Registration Help</h3>
</header> </header>
% if has_extauth_info is UNDEFINED:
<div class="cta"> <div class="cta">
<h3>Already registered?</h3> <h3>Already registered?</h3>
<p class="instructions"> <p class="instructions">
...@@ -254,6 +282,8 @@ ...@@ -254,6 +282,8 @@
</a> </a>
</p> </p>
</div> </div>
% endif
## TODO: Use a %block tag or something to allow themes to ## TODO: Use a %block tag or something to allow themes to
## override in a more generalizable fashion. ## override in a more generalizable fashion.
......
...@@ -32,11 +32,23 @@ ...@@ -32,11 +32,23 @@
<label data-field="name" for="signup_fullname">Full Name *</label> <label data-field="name" for="signup_fullname">Full Name *</label>
<input id="signup_fullname" type="text" name="name" placeholder="e.g. Your Name (for certificates)" required /> <input id="signup_fullname" type="text" name="name" placeholder="e.g. Your Name (for certificates)" required />
% else: % else:
<p><i>Welcome</i> ${extauth_email}</p><br/> <p><i>Welcome</i> ${extauth_id}</p><br/>
<p><i>Enter a public username:</i></p> <p><i>Enter a public username:</i></p>
<label data-field="username" for="signup_username">Public Username *</label> <label data-field="username" for="signup_username">Public Username *</label>
<input id="signup_username" type="text" name="username" value="${extauth_username}" placeholder="e.g. yourname (shown on forums)" required /> <input id="signup_username" type="text" name="username" value="${extauth_username}" placeholder="e.g. yourname (shown on forums)" required />
% if ask_for_email:
<label data-field="email" for="signup_email">E-mail *</label>
<input id="signup_email" type="email" name="email" placeholder="e.g. yourname@domain.com" required />
% endif
% if ask_for_fullname:
<label data-field="name" for="signup_fullname">Full Name *</label>
<input id="signup_fullname" type="text" name="name" placeholder="e.g. Your Name (for certificates)" required />
% endif
% endif % endif
</div> </div>
......
...@@ -364,6 +364,21 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID'): ...@@ -364,6 +364,21 @@ if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'), url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
) )
if settings.MITX_FEATURES.get('AUTH_USE_SHIB'):
urlpatterns += (
url(r'^shib-login/$', 'external_auth.views.shib_login', name='shib-login'),
)
if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'):
urlpatterns += (
url(r'^course_specific_login/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
'external_auth.views.course_specific_login', name='course-specific-login'),
url(r'^course_specific_register/(?P<course_id>[^/]+/[^/]+/[^/]+)/$',
'external_auth.views.course_specific_register', name='course-specific-register'),
)
if settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'): if settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
urlpatterns += ( urlpatterns += (
url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'), url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'),
......
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.envs.aws")
os.environ.setdefault("SERVICE_VARIANT", "lms")
# This application object is used by the development server
# as well as any WSGI server configured to use this file.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from django.conf import settings
from xmodule.modulestore.django import modulestore
for store_name in settings.MODULESTORE:
modulestore(store_name)
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