Commit b1494da0 by Calen Pennington

Merge pull request #330 from MITx/ExternalAuth

External auth
parents 3b6f33f5 dff380e8
'''
django admin pages for courseware model
'''
from external_auth.models import *
from django.contrib import admin
admin.site.register(ExternalAuthMap)
"""
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 mitx dir
2. django-admin.py schemamigration student --auto --settings=lms.envs.dev --pythonpath=. description_of_your_change
3. Add the migration file created in mitx/common/djangoapps/external_auth/migrations/
"""
from django.db import models
from django.contrib.auth.models import User
class ExternalAuthMap(models.Model):
class Meta:
unique_together = (('external_id', 'external_domain'), )
external_id = models.CharField(max_length=255, db_index=True)
external_domain = models.CharField(max_length=255, db_index=True)
external_credentials = models.TextField(blank=True) # JSON dictionary
external_email = models.CharField(max_length=255, db_index=True)
external_name = models.CharField(blank=True,max_length=255, db_index=True)
user = models.OneToOneField(User, unique=True, db_index=True, null=True)
internal_password = models.CharField(blank=True, max_length=31) # randomly generated
dtcreated = models.DateTimeField('creation date',auto_now_add=True)
dtsignup = models.DateTimeField('signup date',null=True) # set after signup
def __unicode__(self):
s = "[%s] = (%s / %s)" % (self.external_id, self.external_name, self.external_email)
return s
import json
import logging
import random
import re
import string
from external_auth.models import ExternalAuthMap
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, authenticate, login
from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.shortcuts import redirect
from django.template import RequestContext
from mitxmako.shortcuts import render_to_response, render_to_string
try:
from django.views.decorators.csrf import csrf_exempt
except ImportError:
from django.contrib.csrf.middleware import csrf_exempt
from django_future.csrf import ensure_csrf_cookie
from util.cache import cache_if_anonymous
from django_openid_auth import auth as openid_auth
from openid.consumer.consumer import (Consumer, SUCCESS, CANCEL, FAILURE)
import django_openid_auth.views as openid_views
import student.views as student_views
log = logging.getLogger("mitx.external_auth")
@csrf_exempt
def default_render_failure(request, message, status=403, template_name='extauth_failure.html', exception=None):
"""Render an Openid error page to the user."""
message = "In openid_failure " + message
log.debug(message)
data = render_to_string( template_name, dict(message=message, exception=exception))
return HttpResponse(data, status=status)
#-----------------------------------------------------------------------------
# Openid
def edXauth_generate_password(length=12, chars=string.letters + string.digits):
"""Generate internal password for externally authenticated user"""
return ''.join([random.choice(chars) for i in range(length)])
@csrf_exempt
def edXauth_openid_login_complete(request, redirect_field_name=REDIRECT_FIELD_NAME, render_failure=None):
"""Complete the openid login process"""
redirect_to = request.REQUEST.get(redirect_field_name, '')
render_failure = render_failure or \
getattr(settings, 'OPENID_RENDER_FAILURE', None) or \
default_render_failure
openid_response = openid_views.parse_openid_response(request)
if not openid_response:
return render_failure(request, 'This is an OpenID relying party endpoint.')
if openid_response.status == SUCCESS:
external_id = openid_response.identity_url
oid_backend = openid_auth.OpenIDBackend()
details = oid_backend._extract_user_details(openid_response)
log.debug('openid success, details=%s' % details)
return edXauth_external_login_or_signup(request,
external_id,
"openid:%s" % settings.OPENID_SSO_SERVER_URL,
details,
details.get('email',''),
'%s %s' % (details.get('first_name',''),details.get('last_name',''))
)
return render_failure(request, 'Openid failure')
#-----------------------------------------------------------------------------
# generic external auth login or signup
def edXauth_external_login_or_signup(request, external_id, external_domain, credentials, email, fullname,
retfun=None):
# see if we have a map from this external_id to an edX username
try:
eamap = ExternalAuthMap.objects.get(external_id = external_id,
external_domain = external_domain,
)
log.debug('Found eamap=%s' % eamap)
except ExternalAuthMap.DoesNotExist:
# go render form for creating edX user
eamap = ExternalAuthMap(external_id = external_id,
external_domain = external_domain,
external_credentials = json.dumps(credentials),
)
eamap.external_email = email
eamap.external_name = fullname
eamap.internal_password = edXauth_generate_password()
log.debug('created eamap=%s' % eamap)
eamap.save()
internal_user = eamap.user
if internal_user is None:
log.debug('ExtAuth: no user for %s yet, doing signup' % eamap.external_email)
return edXauth_signup(request, eamap)
uname = internal_user.username
user = authenticate(username=uname, password=eamap.internal_password)
if user is None:
log.warning("External Auth Login failed for %s / %s" % (uname,eamap.internal_password))
return edXauth_signup(request, eamap)
if not user.is_active:
log.warning("External Auth: user %s is not active" % (uname))
# TODO: improve error page
return render_failure(request, 'Account not yet activated: please look for link in your email')
login(request, user)
request.session.set_expiry(0)
student_views.try_change_enrollment(request)
log.info("Login success - {0} ({1})".format(user.username, user.email))
if retfun is None:
return redirect('/')
return retfun()
#-----------------------------------------------------------------------------
# generic external auth signup
@ensure_csrf_cookie
@cache_if_anonymous
def edXauth_signup(request, eamap=None):
"""
Present form to complete for signup via external authentication.
Even though the user has external credentials, he/she still needs
to create an account on the edX system, and fill in the user
registration form.
eamap is an ExteralAuthMap object, specifying the external user
for which to complete the signup.
"""
if eamap is None:
pass
request.session['ExternalAuthMap'] = eamap # save this for use by student.views.create_account
context = {'has_extauth_info': True,
'show_signup_immediately' : True,
'extauth_email': eamap.external_email,
'extauth_username' : eamap.external_name.split(' ')[0],
'extauth_name': eamap.external_name,
}
log.debug('ExtAuth: doing signup for %s' % eamap.external_email)
return student_views.main_index(extra_context=context)
#-----------------------------------------------------------------------------
# MIT SSL
def ssl_dn_extract_info(dn):
'''
Extract username, email address (may be anyuser@anydomain.com) and full name
from the SSL DN string. Return (user,email,fullname) if successful, and None
otherwise.
'''
ss = re.search('/emailAddress=(.*)@([^/]+)', dn)
if ss:
user = ss.group(1)
email = "%s@%s" % (user, ss.group(2))
else:
return None
ss = re.search('/CN=([^/]+)/', dn)
if ss:
fullname = ss.group(1)
else:
return None
return (user, email, fullname)
@csrf_exempt
def edXauth_ssl_login(request):
"""
This is called by student.views.index when MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
Used for MIT user authentication. This presumes the web server (nginx) has been configured
to require specific client certificates.
If the incoming protocol is HTTPS (SSL) then authenticate via client certificate.
The certificate provides user email and fullname; this populates the ExternalAuthMap.
The user is nevertheless still asked to complete the edX signup.
Else continues on with student.views.main_index, and no authentication.
"""
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use
cert = request.META.get(certkey,'')
if not cert:
cert = request.META.get('HTTP_'+certkey,'')
if not cert:
try:
cert = request._req.subprocess_env.get(certkey,'') # try the direct apache2 SSL key
except Exception as err:
pass
if not cert:
# no certificate information - go onward to main index
return student_views.main_index()
(user, email, fullname) = ssl_dn_extract_info(cert)
return edXauth_external_login_or_signup(request,
external_id=email,
external_domain="ssl:MIT",
credentials=cert,
email=email,
fullname=fullname,
retfun = student_views.main_index)
......@@ -23,7 +23,6 @@ from django.http import HttpResponse, Http404
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse
#from BeautifulSoup import BeautifulSoup
from bs4 import BeautifulSoup
from django.core.cache import cache
......@@ -61,6 +60,19 @@ def index(request):
if settings.COURSEWARE_ENABLED and request.user.is_authenticated():
return redirect(reverse('dashboard'))
if settings.MITX_FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
from external_auth.views import edXauth_ssl_login
return edXauth_ssl_login(request)
return main_index()
def main_index(extra_context = {}):
'''
Render the edX main page.
extra_context is used to allow immediate display of certain modal windows, eg signup,
as used by external_auth.
'''
feed_data = cache.get("students_index_rss_feed_data")
if feed_data == None:
if hasattr(settings, 'RSS_URL'):
......@@ -81,8 +93,9 @@ def index(request):
for course in courses:
universities[course.org].append(course)
return render_to_response('index.html', {'universities': universities, 'entries': entries})
context = {'universities': universities, 'entries': entries}
context.update(extra_context)
return render_to_response('index.html', context)
def course_from_id(id):
course_loc = CourseDescriptor.id_to_location(id)
......@@ -257,11 +270,26 @@ def change_setting(request):
@ensure_csrf_cookie
def create_account(request, post_override=None):
''' JSON call to enroll in the course. '''
'''
JSON call to create new edX account.
Used by form in signup_modal.html, which is included into navigation.html
'''
js = {'success': False}
post_vars = post_override if post_override else request.POST
# 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
DoExternalAuth = 'ExternalAuthMap' in request.session
if DoExternalAuth:
eamap = request.session['ExternalAuthMap']
email = eamap.external_email
name = eamap.external_name
password = eamap.internal_password
post_vars = dict(post_vars.items())
post_vars.update(dict(email=email, name=name, password=password))
log.debug('extauth test: post_vars = %s' % post_vars)
# Confirm we have a properly formed request
for a in ['username', 'email', 'password', 'name']:
if a not in post_vars:
......@@ -356,6 +384,7 @@ def create_account(request, post_override=None):
'key': r.activation_key,
}
# composes activation email
subject = render_to_string('emails/activation_email_subject.txt', d)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
......@@ -382,6 +411,17 @@ def create_account(request, post_override=None):
try_change_enrollment(request)
if DoExternalAuth:
eamap.user = login_user
eamap.dtsignup = datetime.datetime.now()
eamap.save()
log.debug('Updated ExternalAuthMap for %s to be %s' % (post_vars['username'],eamap))
if settings.MITX_FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
log.debug('bypassing activation email')
login_user.is_active = True
login_user.save()
js = {'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json")
......
"""
User authentication backend for ssl (no pw required)
"""
from django.conf import settings
from django.contrib import auth
from django.contrib.auth.models import User, check_password
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.core.exceptions import ImproperlyConfigured
import os
import string
import re
from random import choice
from student.models import UserProfile
#-----------------------------------------------------------------------------
def ssl_dn_extract_info(dn):
'''
Extract username, email address (may be anyuser@anydomain.com) and full name
from the SSL DN string. Return (user,email,fullname) if successful, and None
otherwise.
'''
ss = re.search('/emailAddress=(.*)@([^/]+)', dn)
if ss:
user = ss.group(1)
email = "%s@%s" % (user, ss.group(2))
else:
return None
ss = re.search('/CN=([^/]+)/', dn)
if ss:
fullname = ss.group(1)
else:
return None
return (user, email, fullname)
def check_nginx_proxy(request):
'''
Check for keys in the HTTP header (META) to se if we are behind an ngix reverse proxy.
If so, get user info from the SSL DN string and return that, as (user,email,fullname)
'''
m = request.META
if m.has_key('HTTP_X_REAL_IP'): # we're behind a nginx reverse proxy, which has already done ssl auth
if not m.has_key('HTTP_SSL_CLIENT_S_DN'):
return None
dn = m['HTTP_SSL_CLIENT_S_DN']
return ssl_dn_extract_info(dn)
return None
#-----------------------------------------------------------------------------
def get_ssl_username(request):
x = check_nginx_proxy(request)
if x:
return x[0]
env = request._req.subprocess_env
if env.has_key('SSL_CLIENT_S_DN_Email'):
email = env['SSL_CLIENT_S_DN_Email']
user = email[:email.index('@')]
return user
return None
#-----------------------------------------------------------------------------
class NginxProxyHeaderMiddleware(RemoteUserMiddleware):
'''
Django "middleware" function for extracting user information from HTTP request.
'''
# this field is generated by nginx's reverse proxy
header = 'HTTP_SSL_CLIENT_S_DN' # specify the request.META field to use
def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The Django remote user auth middleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE_CLASSES setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the RemoteUserMiddleware class.")
#raise ImproperlyConfigured('[ProxyHeaderMiddleware] request.META=%s' % repr(request.META))
try:
username = request.META[self.header] # try the nginx META key first
except KeyError:
try:
env = request._req.subprocess_env # else try the direct apache2 SSL key
if env.has_key('SSL_CLIENT_S_DN'):
username = env['SSL_CLIENT_S_DN']
else:
raise ImproperlyConfigured('no ssl key, env=%s' % repr(env))
username = ''
except:
# If specified header doesn't exist then return (leaving
# request.user set to AnonymousUser by the
# AuthenticationMiddleware).
return
# If the user is already authenticated and that user is the user we are
# getting passed in the headers, then the correct user is already
# persisted in the session and we don't need to continue.
#raise ImproperlyConfigured('[ProxyHeaderMiddleware] username=%s' % username)
if request.user.is_authenticated():
if request.user.username == self.clean_username(username, request):
#raise ImproperlyConfigured('%s already authenticated (%s)' % (username,request.user.username))
return
# We are seeing this user for the first time in this session, attempt
# to authenticate the user.
#raise ImproperlyConfigured('calling auth.authenticate, remote_user=%s' % username)
user = auth.authenticate(remote_user=username)
if user:
# User is valid. Set request.user and persist user in the session
# by logging the user in.
request.user = user
if settings.DEBUG: print "[ssl_auth.ssl_auth.NginxProxyHeaderMiddleware] logging in user=%s" % user
auth.login(request, user)
def clean_username(self, username, request):
'''
username is the SSL DN string - extract the actual username from it and return
'''
info = ssl_dn_extract_info(username)
if not info:
return None
(username, email, fullname) = info
return username
#-----------------------------------------------------------------------------
class SSLLoginBackend(ModelBackend):
'''
Django authentication back-end which auto-logs-in a user based on having
already authenticated with an MIT certificate (SSL).
'''
def authenticate(self, username=None, password=None, remote_user=None):
# remote_user is from the SSL_DN string. It will be non-empty only when
# the user has already passed the server authentication, which means
# matching with the certificate authority.
if not remote_user:
# no remote_user, so check username (but don't auto-create user)
if not username:
return None
return None # pass on to another authenticator backend
#raise ImproperlyConfigured("in SSLLoginBackend, username=%s, remote_user=%s" % (username,remote_user))
try:
user = User.objects.get(username=username) # if user already exists don't create it
return user
except User.DoesNotExist:
return None
return None
#raise ImproperlyConfigured("in SSLLoginBackend, username=%s, remote_user=%s" % (username,remote_user))
#if not os.environ.has_key('HTTPS'):
# return None
#if not os.environ.get('HTTPS')=='on': # only use this back-end if HTTPS on
# return None
def GenPasswd(length=8, chars=string.letters + string.digits):
return ''.join([choice(chars) for i in range(length)])
# convert remote_user to user, email, fullname
info = ssl_dn_extract_info(remote_user)
#raise ImproperlyConfigured("[SSLLoginBackend] looking up %s" % repr(info))
if not info:
#raise ImproperlyConfigured("[SSLLoginBackend] remote_user=%s, info=%s" % (remote_user,info))
return None
(username, email, fullname) = info
try:
user = User.objects.get(username=username) # if user already exists don't create it
except User.DoesNotExist:
if not settings.DEBUG:
raise "User does not exist. Not creating user; potential schema consistency issues"
#raise ImproperlyConfigured("[SSLLoginBackend] creating %s" % repr(info))
user = User(username=username, password=GenPasswd()) # create new User
user.is_staff = False
user.is_superuser = False
# get first, last name from fullname
name = fullname
if not name.count(' '):
user.first_name = " "
user.last_name = name
mn = ''
else:
user.first_name = name[:name.find(' ')]
ml = name[name.find(' '):].strip()
if ml.count(' '):
user.last_name = ml[ml.rfind(' '):]
mn = ml[:ml.rfind(' ')]
else:
user.last_name = ml
mn = ''
# set email
user.email = email
# cleanup last name
user.last_name = user.last_name.strip()
# save
user.save()
# auto-create user profile
up = UserProfile(user=user)
up.name = fullname
up.save()
#tui = user.get_profile()
#tui.middle_name = mn
#tui.role = 'Misc'
#tui.section = None # no section assigned at first
#tui.save()
# return None
return user
def get_user(self, user_id):
#if not os.environ.has_key('HTTPS'):
# return None
#if not os.environ.get('HTTPS')=='on': # only use this back-end if HTTPS on
# return None
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#-----------------------------------------------------------------------------
# OLD!
class AutoLoginBackend:
def authenticate(self, username=None, password=None):
raise ImproperlyConfigured("in AutoLoginBackend, username=%s" % username)
if not os.environ.has_key('HTTPS'):
return None
if not os.environ.get('HTTPS') == 'on':# only use this back-end if HTTPS on
return None
def GenPasswd(length=8, chars=string.letters + string.digits):
return ''.join([choice(chars) for i in range(length)])
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(username=username, password=GenPasswd())
user.is_staff = False
user.is_superuser = False
# get first, last name
name = os.environ.get('SSL_CLIENT_S_DN_CN').strip()
if not name.count(' '):
user.first_name = " "
user.last_name = name
mn = ''
else:
user.first_name = name[:name.find(' ')]
ml = name[name.find(' '):].strip()
if ml.count(' '):
user.last_name = ml[ml.rfind(' '):]
mn = ml[:ml.rfind(' ')]
else:
user.last_name = ml
mn = ''
# get email
user.email = os.environ.get('SSL_CLIENT_S_DN_Email')
# save
user.save()
tui = user.get_profile()
tui.middle_name = mn
tui.role = 'Misc'
tui.section = None# no section assigned at first
tui.save()
# return None
return user
def get_user(self, user_id):
if not os.environ.has_key('HTTPS'):
return None
if not os.environ.get('HTTPS') == 'on':# only use this back-end if HTTPS on
return None
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
......@@ -58,6 +58,21 @@ CACHE_TIMEOUT = 0
# Dummy secret key for dev
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
################################ OpenID Auth #################################
MITX_FEATURES['AUTH_USE_OPENID'] = True
MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('django_openid_auth',)
OPENID_CREATE_USERS = False
OPENID_UPDATE_DETAILS_FROM_SREG = True
OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id' # TODO: accept more endpoints
OPENID_USE_AS_ADMIN_LOGIN = False
################################ MIT Certificates SSL Auth #################################
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
......
......@@ -7,142 +7,110 @@ sessions. Assumes structure:
/mitx # The location of this repo
/log # Where we're going to write log files
"""
import socket
if 'eecs1' in socket.gethostname():
MITX_ROOT_URL = '/mitx2'
from .common import *
from .logsettings import get_logger_config
from .dev import *
if 'eecs1' in socket.gethostname():
# MITX_ROOT_URL = '/mitx2'
MITX_ROOT_URL = 'https://eecs1.mit.edu/mitx2'
#-----------------------------------------------------------------------------
# edx4edx content server
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
MITX_FEATURES['REROUTE_ACTIVATION_EMAIL'] = 'ichuang@mit.edu'
EDX4EDX_ROOT = ENV_ROOT / "data/edx4edx"
#EMAIL_BACKEND = 'django_ses.SESBackend'
#-----------------------------------------------------------------------------
# ichuang
DEBUG = True
ENABLE_MULTICOURSE = True # set to False to disable multicourse display (see lib.util.views.mitxhome)
QUICKEDIT = False
MAKO_TEMPLATES['course'] = [DATA_DIR, EDX4EDX_ROOT ]
#MITX_FEATURES['USE_DJANGO_PIPELINE'] = False
MITX_FEATURES['DISPLAY_HISTOGRAMS_TO_STAFF'] = False
MITX_FEATURES['DISPLAY_EDIT_LINK'] = True
MITX_FEATURES['DEBUG_LEVEL'] = 10 # 0 = lowest level, least verbose, 255 = max level, most verbose
COURSE_SETTINGS = {'6.002x_Fall_2012': {'number' : '6.002x',
'title' : 'Circuits and Electronics',
'xmlpath': '/6002x-fall-2012/',
'active' : True,
'default_chapter' : 'Week_1',
'default_section' : 'Administrivia_and_Circuit_Elements',
'location': 'i4x://edx/6002xs12/course/6.002x_Fall_2012',
},
'8.02_Spring_2013': {'number' : '8.02x',
'title' : 'Electricity & Magnetism',
'xmlpath': '/802x/',
'github_url': 'https://github.com/MITx/8.02x',
'active' : True,
'default_chapter' : 'Introduction',
'default_section' : 'Introduction_%28Lewin_2002%29',
},
'6.189_Spring_2013': {'number' : '6.189x',
'title' : 'IAP Python Programming',
'xmlpath': '/6.189x/',
'github_url': 'https://github.com/MITx/6.189x',
'active' : True,
'default_chapter' : 'Week_1',
'default_section' : 'Variables_and_Binding',
},
'8.01_Fall_2012': {'number' : '8.01x',
'title' : 'Mechanics',
'xmlpath': '/8.01x/',
'github_url': 'https://github.com/MITx/8.01x',
'active': True,
'default_chapter' : 'Mechanics_Online_Spring_2012',
'default_section' : 'Introduction_to_the_course',
'location': 'i4x://edx/6002xs12/course/8.01_Fall_2012',
},
'edx4edx': {'number' : 'edX.01',
'title' : 'edx4edx: edX Author Course',
'xmlpath': '/edx4edx/',
'github_url': 'https://github.com/MITx/edx4edx',
'active' : True,
'default_chapter' : 'Introduction',
'default_section' : 'edx4edx_Course',
'location': 'i4x://edx/6002xs12/course/edx4edx',
},
'7.03x_Fall_2012': {'number' : '7.03x',
'title' : 'Genetics',
'xmlpath': '/7.03x/',
'github_url': 'https://github.com/MITx/7.03x',
'active' : True,
'default_chapter' : 'Week_2',
'default_section' : 'ps1_question_1',
},
'3.091x_Fall_2012': {'number' : '3.091x',
'title' : 'Introduction to Solid State Chemistry',
'xmlpath': '/3.091x/',
'github_url': 'https://github.com/MITx/3.091x',
'active' : True,
'default_chapter' : 'Week_1',
'default_section' : 'Problem_Set_1',
},
'18.06x_Linear_Algebra': {'number' : '18.06x',
'title' : 'Linear Algebra',
'xmlpath': '/18.06x/',
'github_url': 'https://github.com/MITx/18.06x',
'default_chapter' : 'Unit_1',
'default_section' : 'Midterm_1',
'active' : True,
},
'6.00x_Fall_2012': {'number' : '6.00x',
'title' : 'Introduction to Computer Science and Programming',
'xmlpath': '/6.00x/',
'github_url': 'https://github.com/MITx/6.00x',
'active' : True,
'default_chapter' : 'Week_0',
'default_section' : 'Problem_Set_0',
'location': 'i4x://edx/6002xs12/course/6.00x_Fall_2012',
},
'7.00x_Fall_2012': {'number' : '7.00x',
'title' : 'Introduction to Biology',
'xmlpath': '/7.00x/',
'github_url': 'https://github.com/MITx/7.00x',
'active' : True,
'default_chapter' : 'Unit 1',
'default_section' : 'Introduction',
},
}
TEMPLATE_DEBUG = True
#-----------------------------------------------------------------------------
MITX_FEATURES['DISABLE_START_DATES'] = True
MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + (
'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
)
WIKI_ENABLED = True
AUTHENTICATION_BACKENDS = (
'ssl_auth.ssl_auth.SSLLoginBackend',
'django.contrib.auth.backends.ModelBackend',
)
LOGGING = get_logger_config(ENV_ROOT / "log",
logging_env="dev",
tracking_filename="tracking.log",
debug=True)
INSTALLED_APPS = INSTALLED_APPS + (
'ssl_auth',
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "mitx.db",
}
}
CACHES = {
# This is the cache used for most things. Askbot will not work without a
# functioning cache -- it relies on caching to load its settings in places.
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/'
LOGIN_URL = MITX_ROOT_URL + '/'
# The general cache is what you get if you use our util.cache. It's used for
# things like caching the course.xml file for different A/B test groups.
# We set it to be a DummyCache to force reloading of course.xml in dev.
# In staging environments, we would grab VERSION from data uploaded by the
# push process.
'general': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'KEY_PREFIX': 'general',
'VERSION': 4,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
}
# Dummy secret key for dev
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
################################ OpenID Auth #################################
MITX_FEATURES['AUTH_USE_OPENID'] = True
INSTALLED_APPS += ('external_auth',)
INSTALLED_APPS += ('django_openid_auth',)
#INSTALLED_APPS += ('ssl_auth',)
#MIDDLEWARE_CLASSES += (
# #'ssl_auth.ssl_auth.NginxProxyHeaderMiddleware', # ssl authentication behind nginx proxy
# )
#AUTHENTICATION_BACKENDS = (
# 'django_openid_auth.auth.OpenIDBackend',
# 'django.contrib.auth.backends.ModelBackend',
# )
OPENID_CREATE_USERS = False
OPENID_UPDATE_DETAILS_FROM_SREG = True
OPENID_SSO_SERVER_URL = 'https://www.google.com/accounts/o8/id'
OPENID_USE_AS_ADMIN_LOGIN = False
#import external_auth.views as edXauth
#OPENID_RENDER_FAILURE = edXauth.edXauth_openid
################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = (
'debug_toolbar.panels.version.VersionDebugPanel',
'debug_toolbar.panels.timer.TimerDebugPanel',
'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
'debug_toolbar.panels.headers.HeaderDebugPanel',
'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
'debug_toolbar.panels.sql.SQLDebugPanel',
'debug_toolbar.panels.signals.SignalDebugPanel',
'debug_toolbar.panels.logger.LoggingPanel',
# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
# hit twice). So you can uncomment when you need to diagnose performance
# problems, but you shouldn't leave it on.
# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
)
############################ FILE UPLOADS (ASKBOT) #############################
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = ENV_ROOT / "uploads"
MEDIA_URL = "/static/uploads/"
STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
FILE_UPLOAD_TEMP_DIR = ENV_ROOT / "uploads"
FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.MemoryFileUploadHandler',
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)
########################### PIPELINE #################################
PIPELINE_SASS_ARGUMENTS = '-r {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>OpenID failed</title>
</head>
<body>
<h1>OpenID failed</h1>
<p>${message}</p>
</body>
</html>
......@@ -144,3 +144,31 @@
<iframe width="640" height="360" src="http://www.youtube.com/embed/C2OQ51tu7W4?showinfo=0" frameborder="0" allowfullscreen></iframe>
</div>
</section>
% if show_signup_immediately is not UNDEFINED:
<script type="text/javascript">
function dosignup(){
comp = document.getElementById('signup');
try { //in firefox
comp.click();
return;
} catch(ex) {}
try { // in old chrome
if(document.createEvent) {
var e = document.createEvent('MouseEvents');
e.initEvent( 'click', true, true );
comp.dispatchEvent(e);
return;
}
} catch(ex) {}
try { // in IE, safari
if(document.createEventObject) {
var evObj = document.createEventObject();
comp.fireEvent("onclick", evObj);
return;
}
} catch(ex) {}
}
$(window).load(dosignup);
</script>
% endif
......@@ -27,6 +27,9 @@
<span>Not enrolled? <a href="#signup-modal" class="close-login" rel="leanModal">Sign up.</a></span>
<a href="#forgot-password-modal" rel="leanModal" class="pwd-reset">Forgot password?</a>
</p>
<p>
<a href="${MITX_ROOT_URL}/openid/login/">login via openid</a>
</p>
</section>
<div class="close-modal">
......
......@@ -19,6 +19,7 @@
<div id="register_error" name="register_error"></div>
<div class="input-group">
% if has_extauth_info is UNDEFINED:
<label data-field="email">E-mail*</label>
<input name="email" type="email" placeholder="E-mail*">
<label data-field="password">Password*</label>
......@@ -27,6 +28,12 @@
<input name="username" type="text" placeholder="Public Username*">
<label data-field="name">Full Name</label>
<input name="name" type="text" placeholder="Full Name*">
% else:
<p><i>Welcome</i> ${extauth_email}</p><br/>
<p><i>Enter a public username:</i></p>
<label data-field="username">Public Username*</label>
<input name="username" type="text" value="${extauth_username}" placeholder="Public Username*">
% endif
</div>
<div class="input-group">
......@@ -93,11 +100,13 @@
</div>
</form>
% if has_extauth_info is UNDEFINED:
<section class="login-extra">
<p>
<span>Already have an account? <a href="#login-modal" class="close-signup" rel="leanModal">Login.</a></span>
</p>
</section>
% endif
</div>
......
......@@ -162,12 +162,18 @@ if settings.DEBUG:
## Jasmine
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
if settings.MITX_FEATURES.get('AUTH_USE_OPENID'):
urlpatterns += (
url(r'^openid/login/$', 'django_openid_auth.views.login_begin', name='openid-login'),
url(r'^openid/complete/$', 'external_auth.views.edXauth_openid_login_complete', name='openid-complete'),
url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
)
urlpatterns = patterns(*urlpatterns)
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
#Custom error pages
handler404 = 'static_template_view.views.render_404'
handler500 = 'static_template_view.views.render_500'
......
......@@ -8,6 +8,7 @@ lxml
boto
mako
python-memcached
python-openid
path.py
django_debug_toolbar
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
......@@ -37,6 +38,7 @@ django-jasmine
django-keyedcache
django-mako
django-masquerade
django-openid-auth
django-robots
django-ses
django-storages
......
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