diff --git a/common/djangoapps/external_auth/views.py b/common/djangoapps/external_auth/views.py
index 9e41d31c77bea26b43278b34ef269e405ca7770e..5cf21ca68dd9c269b71922fe30d82561e708a6a9 100644
--- a/common/djangoapps/external_auth/views.py
+++ b/common/djangoapps/external_auth/views.py
@@ -4,19 +4,18 @@ import logging
 import random
 import re
 import string
+import fnmatch
 
 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 student.models import UserProfile
 
-from django.core.urlresolvers import reverse
 from django.http import HttpResponse, HttpResponseRedirect
-from django.shortcuts import render_to_response
+from django.utils.http import urlquote
 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
@@ -24,100 +23,132 @@ 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
+from django_openid_auth import auth as openid_auth
+from openid.consumer.consumer import SUCCESS
+
+from openid.server.server import Server
+from openid.server.trustroot import TrustRoot
+from openid.store.filestore import FileOpenIDStore
+from openid.extensions import ax, sreg
 
 import student.views as student_views
 
 log = logging.getLogger("mitx.external_auth")
 
+
+# -----------------------------------------------------------------------------
+# OpenID Common
+# -----------------------------------------------------------------------------
+
+
 @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))
+def default_render_failure(request,
+                           message,
+                           status=403,
+                           template_name='extauth_failure.html',
+                           exception=None):
+    """Render an Openid error page to the user"""
+
+    log.debug("In openid_failure " + 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):
+# -----------------------------------------------------------------------------
+# OpenID Authentication
+# -----------------------------------------------------------------------------
+
+
+def 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)])
+    choice = random.SystemRandom().choice
+    return ''.join([choice(chars) for i in range(length)])
+
 
 @csrf_exempt
-def edXauth_openid_login_complete(request,  redirect_field_name=REDIRECT_FIELD_NAME, render_failure=None):
+def 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
-                                                   
+    render_failure = (render_failure 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.')
+        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()
+        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',''))
-                                                )
-                                 
+        url = getattr(settings, 'OPENID_SSO_SERVER_URL', None)
+        external_domain = "openid:%s" % url
+        fullname = '%s %s' % (details.get('first_name', ''),
+                              details.get('last_name', ''))
+
+        return external_login_or_signup(request,
+                                        external_id,
+                                        external_domain,
+                                        details,
+                                        details.get('email', ''),
+                                        fullname)
+
     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):
+def external_login_or_signup(request,
+                             external_id,
+                             external_domain,
+                             credentials,
+                             email,
+                             fullname,
+                             retfun=None):
+    """Generic external auth login or signup"""
+
     # 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,
-                                            )
+        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 = 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.internal_password = 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)
-    
+        log.debug('No user for %s yet, doing signup' % eamap.external_email)
+        return 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)
+        log.warning("External Auth Login failed for %s / %s" %
+                    (uname, eamap.internal_password))
+        return signup(request, eamap)
 
     if not user.is_active:
-        log.warning("External Auth: user %s is not active" % (uname))
+        log.warning("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')
-     
+        msg = 'Account not yet activated: please look for link in your email'
+        return default_render_failure(request, msg)
+
     login(request, user)
     request.session.set_expiry(0)
     student_views.try_change_enrollment(request)
@@ -125,14 +156,11 @@ def edXauth_external_login_or_signup(request, external_id, external_domain, cred
     if retfun is None:
         return redirect('/')
     return retfun()
-        
-    
-#-----------------------------------------------------------------------------
-# generic external auth signup
+
 
 @ensure_csrf_cookie
 @cache_if_anonymous
-def edXauth_signup(request, eamap=None):
+def signup(request, eamap=None):
     """
     Present form to complete for signup via external authentication.
     Even though the user has external credentials, he/she still needs
@@ -142,32 +170,39 @@ def edXauth_signup(request, eamap=None):
     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
-    
+    # save this for use by student.views.create_account
+    request.session['ExternalAuthMap'] = eamap
+
+    # default conjoin name, no spaces
+    username = eamap.external_name.replace(' ', '')
+
     context = {'has_extauth_info': True,
-               'show_signup_immediately' : True,
+               'show_signup_immediately': True,
                'extauth_email': eamap.external_email,
-               'extauth_username' : eamap.external_name.replace(' ',''), # default - conjoin name, no spaces
+               'extauth_username': username,
                'extauth_name': eamap.external_name,
                }
-    
-    log.debug('ExtAuth: doing signup for %s' % eamap.external_email)
+
+    log.debug('Doing signup for %s' % eamap.external_email)
 
     return student_views.index(request, 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.
-    '''
+    """
+    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)
@@ -181,40 +216,333 @@ def ssl_dn_extract_info(dn):
         return None
     return (user, email, fullname)
 
+
 @csrf_exempt
-def edXauth_ssl_login(request):
+def ssl_login(request):
     """
-    This is called by student.views.index when MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
+    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.
+    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.  
+    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.index, and no authentication.
     """
-    certkey = "SSL_CLIENT_S_DN"	 # specify the request.META field to use
-    
-    cert = request.META.get(certkey,'')
+    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,'')
+        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
+            # try the direct apache2 SSL key
+            cert = request._req.subprocess_env.get(certkey, '')
+        except Exception:
+            cert = None
+
     if not cert:
         # no certificate information - go onward to main index
         return student_views.index(request)
 
     (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 = functools.partial(student_views.index, request))
+
+    retfun = functools.partial(student_views.index, request)
+    return external_login_or_signup(request,
+                                    external_id=email,
+                                    external_domain="ssl:MIT",
+                                    credentials=cert,
+                                    email=email,
+                                    fullname=fullname,
+                                    retfun=retfun)
+
+
+# -----------------------------------------------------------------------------
+# OpenID Provider
+# -----------------------------------------------------------------------------
+
+
+def get_xrds_url(resource, request):
+    """
+    Return the XRDS url for a resource
+    """
+    host = request.META['HTTP_HOST']
+
+    if not host.endswith('edx.org'):
+        return None
+
+    location = host + '/openid/provider/' + resource + '/'
+
+    if request.is_secure():
+        return 'https://' + location
+    else:
+        return 'http://' + location
+
+
+def add_openid_simple_registration(request, response, data):
+    sreg_data = {}
+    sreg_request = sreg.SRegRequest.fromOpenIDRequest(request)
+    sreg_fields = sreg_request.allRequestedFields()
+
+    # if consumer requested simple registration fields, add them
+    if sreg_fields:
+        for field in sreg_fields:
+            if field == 'email' and 'email' in data:
+                sreg_data['email'] = data['email']
+            elif field == 'fullname' and 'fullname' in data:
+                sreg_data['fullname'] = data['fullname']
+
+        # construct sreg response
+        sreg_response = sreg.SRegResponse.extractResponse(sreg_request,
+                                                          sreg_data)
+        sreg_response.toMessage(response.fields)
+
+
+def add_openid_attribute_exchange(request, response, data):
+    try:
+        ax_request = ax.FetchRequest.fromOpenIDRequest(request)
+    except ax.AXError:
+        #  not using OpenID attribute exchange extension
+        pass
+    else:
+        ax_response = ax.FetchResponse()
+
+        # if consumer requested attribute exchange fields, add them
+        if ax_request and ax_request.requested_attributes:
+            for type_uri in ax_request.requested_attributes.iterkeys():
+                email_schema = 'http://axschema.org/contact/email'
+                name_schema = 'http://axschema.org/namePerson'
+                if type_uri == email_schema and 'email' in data:
+                    ax_response.addValue(email_schema, data['email'])
+                elif type_uri == name_schema and 'fullname' in data:
+                    ax_response.addValue(name_schema, data['fullname'])
+
+            # construct ax response
+            ax_response.toMessage(response.fields)
+
+
+def provider_respond(server, request, response, data):
+    """
+    Respond to an OpenID request
+    """
+    # get and add extensions
+    add_openid_simple_registration(request, response, data)
+    add_openid_attribute_exchange(request, response, data)
+
+    # create http response from OpenID response
+    webresponse = server.encodeResponse(response)
+    http_response = HttpResponse(webresponse.body)
+    http_response.status_code = webresponse.code
+
+    # add OpenID headers to response
+    for k, v in webresponse.headers.iteritems():
+        http_response[k] = v
+
+    return http_response
+
+
+def validate_trust_root(openid_request):
+    """
+    Only allow OpenID requests from valid trust roots
+    """
+
+    trusted_roots = getattr(settings, 'OPENID_PROVIDER_TRUSTED_ROOT', None)
+
+    if not trusted_roots:
+        # not using trusted roots
+        return True
+
+    # don't allow empty trust roots
+    if (not hasattr(openid_request, 'trust_root') or
+            not openid_request.trust_root):
+        log.error('no trust_root')
+        return False
+
+    # ensure trust root parses cleanly (one wildcard, of form *.foo.com, etc.)
+    trust_root = TrustRoot.parse(openid_request.trust_root)
+    if not trust_root:
+        log.error('invalid trust_root')
+        return False
+
+    # don't allow empty return tos
+    if (not hasattr(openid_request, 'return_to') or
+            not openid_request.return_to):
+        log.error('empty return_to')
+        return False
+
+    # ensure return to is within trust root
+    if not trust_root.validateURL(openid_request.return_to):
+        log.error('invalid return_to')
+        return False
+
+    # check that the root matches the ones we trust
+    if not any(r for r in trusted_roots if fnmatch.fnmatch(trust_root, r)):
+        log.error('non-trusted root')
+        return False
+
+    return True
+
+
+@csrf_exempt
+def provider_login(request):
+    """
+    OpenID login endpoint
+    """
+
+    # make and validate endpoint
+    endpoint = get_xrds_url('login', request)
+    if not endpoint:
+        return default_render_failure(request, "Invalid OpenID request")
+
+    # initialize store and server
+    store = FileOpenIDStore('/tmp/openid_provider')
+    server = Server(store, endpoint)
+
+    # handle OpenID request
+    querydict = dict(request.REQUEST.items())
+    error = False
+    if 'openid.mode' in request.GET or 'openid.mode' in request.POST:
+        # decode request
+        openid_request = server.decodeRequest(querydict)
+
+        if not openid_request:
+            return default_render_failure(request, "Invalid OpenID request")
+
+        # don't allow invalid and non-trusted trust roots
+        if not validate_trust_root(openid_request):
+            return default_render_failure(request, "Invalid OpenID trust root")
+
+        # checkid_immediate not supported, require user interaction
+        if openid_request.mode == 'checkid_immediate':
+            return provider_respond(server, openid_request,
+                                    openid_request.answer(False), {})
+
+        # checkid_setup, so display login page
+        elif openid_request.mode == 'checkid_setup':
+            if openid_request.idSelect():
+                # remember request and original path
+                request.session['openid_setup'] = {
+                    'request': openid_request,
+                    'url': request.get_full_path()
+                }
+
+                # user failed login on previous attempt
+                if 'openid_error' in request.session:
+                    error = True
+                    del request.session['openid_error']
+
+        # OpenID response
+        else:
+            return provider_respond(server, openid_request,
+                                    server.handleRequest(openid_request), {})
+
+    # handle login
+    if request.method == 'POST' and 'openid_setup' in request.session:
+        # get OpenID request from session
+        openid_setup = request.session['openid_setup']
+        openid_request = openid_setup['request']
+        openid_request_url = openid_setup['url']
+        del request.session['openid_setup']
+
+        # don't allow invalid trust roots
+        if not validate_trust_root(openid_request):
+            return default_render_failure(request, "Invalid OpenID trust root")
+
+        # check if user with given email exists
+        email = request.POST.get('email', None)
+        try:
+            user = User.objects.get(email=email)
+        except User.DoesNotExist:
+            request.session['openid_error'] = True
+            msg = "OpenID login failed - Unknown user email: {0}".format(email)
+            log.warning(msg)
+            return HttpResponseRedirect(openid_request_url)
+
+        # attempt to authenticate user
+        username = user.username
+        password = request.POST.get('password', None)
+        user = authenticate(username=username, password=password)
+        if user is None:
+            request.session['openid_error'] = True
+            msg = "OpenID login failed - password for {0} is invalid"
+            msg = msg.format(email)
+            log.warning(msg)
+            return HttpResponseRedirect(openid_request_url)
+
+        # authentication succeeded, so log user in
+        if user is not None and user.is_active:
+            # remove error from session since login succeeded
+            if 'openid_error' in request.session:
+                del request.session['openid_error']
+
+            # fullname field comes from user profile
+            profile = UserProfile.objects.get(user=user)
+            log.info("OpenID login success - {0} ({1})".format(user.username,
+                                                               user.email))
+
+            # redirect user to return_to location
+            url = endpoint + urlquote(user.username)
+            response = openid_request.answer(True, None, url)
+
+            return provider_respond(server,
+                                    openid_request,
+                                    response,
+                                    {
+                                        'fullname': profile.name,
+                                        'email': user.email
+                                    })
+
+        request.session['openid_error'] = True
+        msg = "Login failed - Account not active for user {0}".format(username)
+        log.warning(msg)
+        return HttpResponseRedirect(openid_request_url)
+
+    # determine consumer domain if applicable
+    return_to = ''
+    if 'openid.return_to' in request.REQUEST:
+        return_to = request.REQUEST['openid.return_to']
+        matches = re.match(r'\w+:\/\/([\w\.-]+)', return_to)
+        return_to = matches.group(1)
+
+    # display login page
+    response = render_to_response('provider_login.html', {
+        'error': error,
+        'return_to': return_to
+    })
+
+    # custom XRDS header necessary for discovery process
+    response['X-XRDS-Location'] = get_xrds_url('xrds', request)
+    return response
+
+
+def provider_identity(request):
+    """
+    XRDS for identity discovery
+    """
+
+    response = render_to_response('identity.xml',
+                                  {'url': get_xrds_url('login', request)},
+                                  mimetype='text/xml')
+
+    # custom XRDS header necessary for discovery process
+    response['X-XRDS-Location'] = get_xrds_url('identity', request)
+    return response
+
+
+def provider_xrds(request):
+    """
+    XRDS for endpoint discovery
+    """
+
+    response = render_to_response('xrds.xml',
+                                  {'url': get_xrds_url('login', request)},
+                                  mimetype='text/xml')
+
+    # custom XRDS header necessary for discovery process
+    response['X-XRDS-Location'] = get_xrds_url('xrds', request)
+    return response
diff --git a/common/lib/capa/capa/templates/filesubmission.html b/common/lib/capa/capa/templates/filesubmission.html
index e9fd7c5674102847c8eea56dc4ddb01066b5b149..630a3222dc1a529a373e8ed04f925a0428e34812 100644
--- a/common/lib/capa/capa/templates/filesubmission.html
+++ b/common/lib/capa/capa/templates/filesubmission.html
@@ -1,17 +1,18 @@
 <section id="filesubmission_${id}" class="filesubmission">
-    <input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" data-required_files="${required_files}" data-allowed_files="${allowed_files}"/><br />
+  <div class="grader-status file">
     % if state == 'unsubmitted':
-    <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
+    <span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
     % elif state == 'correct':
-    <span class="correct" id="status_${id}"></span>
+    <span class="correct" id="status_${id}">Correct</span>
     % elif state == 'incorrect':
-    <span class="incorrect" id="status_${id}"></span>
+    <span class="incorrect" id="status_${id}">Incorrect</span>
     % elif state == 'queued':
-    <span class="processing" id="status_${id}"></span>
+    <span class="processing" id="status_${id}">Queued</span>
     <span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
     % endif
-    <span style="display:none;" class="debug">(${state})</span>
-    <br/>
-    <span class="message">${msg|n}</span>
-    <br/>
+    <p class="debug">${state}</p>
+
+    <input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" data-required_files="${required_files}" data-allowed_files="${allowed_files}"/>
+  </div>
+    <div class="message">${msg|n}</div>
 </section>
diff --git a/common/lib/capa/capa/templates/textbox.html b/common/lib/capa/capa/templates/textbox.html
index 19c43482a876f1c9aa7b1d4860490e799c587e17..271d7795e00b4cc0c235edb38c5d058a90730cee 100644
--- a/common/lib/capa/capa/templates/textbox.html
+++ b/common/lib/capa/capa/templates/textbox.html
@@ -7,26 +7,28 @@
 
   <span id="answer_${id}"></span>
 
-  % if state == 'unsubmitted':
-  <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
-  % elif state == 'correct':
-  <span class="correct" id="status_${id}"></span>
-  % elif state == 'incorrect':
-  <span class="incorrect" id="status_${id}"></span>
-  % elif state == 'queued':
-  <span class="processing" id="status_${id}"></span>
-  <span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
-  % endif
-  % if hidden:
-  <div style="display:none;" name="${hidden}" inputid="input_${id}" />
-  % endif
-  <br/>
-  <span style="display:none;" class="debug">(${state})</span>
-  <br/>
-  <span class="message">${msg|n}</span>
-  <br/>
+  <div class="grader-status">
+    % if state == 'unsubmitted':
+      <span class="unanswered" style="display:inline-block;" id="status_${id}">Unanswered</span>
+    % elif state == 'correct':
+      <span class="correct" id="status_${id}">Correct</span>
+    % elif state == 'incorrect':
+      <span class="incorrect" id="status_${id}">Incorrect</span>
+    % elif state == 'queued':
+      <span class="processing" id="status_${id}">Queued</span>
+      <span style="display:none;" class="xqueue" id="${id}" >${queue_len}</span>
+    % endif
 
-  <br/>
+    % if hidden:
+      <div style="display:none;" name="${hidden}" inputid="input_${id}" />
+    % endif
+
+    <p class="debug">${state}</p>
+  </div>
+
+  <div class="external-grader-message">
+    ${msg|n}
+  </div>
 
   <script>
     // Note: We need to make the area follow the CodeMirror for this to work.
@@ -45,12 +47,4 @@
       });
     });
   </script>
-  <style type="text/css">
-    .CodeMirror {
-        border: 1px solid black;
-        font-size: 14px;
-        line-height: 18px;
-        resize: both;
-    }
-  </style>
 </section>
diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss
index e6ebdb316fbc32a61110e91baa0276a627c6b66c..aea59a4d6367b60fefaa3bede8e4db5979189ee7 100644
--- a/common/lib/xmodule/xmodule/css/capa/display.scss
+++ b/common/lib/xmodule/xmodule/css/capa/display.scss
@@ -16,6 +16,7 @@ h2 {
   }
 }
 
+
 section.problem {
   @media print {
     display: block;
@@ -31,6 +32,7 @@ section.problem {
       display: inline;
   }
 
+
   div {
     p {
       &.answer {
@@ -171,8 +173,54 @@ section.problem {
         top: 6px;
       }
     }
+
+    .grader-status {
+      padding: 9px;
+      background: #F6F6F6;
+      border: 1px solid #ddd;
+      border-top: 0;
+      margin-bottom: 20px;
+      @include clearfix;
+
+      span {
+        text-indent: -9999px;
+        overflow: hidden;
+        display: block;
+        float: left;
+        margin: -7px 7px 0 0;
+      }
+
+      p {
+        line-height: 20px;
+        text-transform: capitalize;
+        margin-bottom: 0;
+        float: left;
+      }
+
+      &.file {
+        background: #FFF;
+        margin-top: 20px;
+        padding: 20px 0 0 0;
+
+        border: {
+          top: 1px solid #eee;
+          right: 0;
+          bottom: 0;
+          left: 0;
+        }
+
+        p.debug {
+          display: none;
+        }
+
+        input {
+          float: left;
+        }
+      }
+    }
   }
 
+
   ul {
     list-style: disc outside none;
     margin-bottom: lh();
@@ -246,6 +294,69 @@ section.problem {
 
   }
 
+  code {
+    margin: 0 2px;
+    padding: 0px 5px;
+    white-space: nowrap;
+    border: 1px solid #EAEAEA;
+    background-color: #F8F8F8;
+    @include border-radius(3px);
+    font-size: .9em;
+  }
+
+  pre {
+    background-color: #F8F8F8;
+    border: 1px solid #CCC;
+    font-size: .9em;
+    line-height: 1.4;
+    overflow: auto;
+    padding: 6px 10px;
+    @include border-radius(3px);
+
+    > code {
+      margin: 0;
+      padding: 0;
+      white-space: pre;
+      border: none;
+      background: transparent;
+    }
+  }
+
+  .CodeMirror {
+    border: 1px solid black;
+    font-size: 14px;
+    line-height: 18px;
+    resize: both;
+
+    pre {
+      @include border-radius(0);
+      border-radius: 0;
+      border-width: 0;
+      margin: 0;
+      padding: 0;
+      background: transparent;
+      font-family: inherit;
+      font-size: inherit;
+      white-space: pre;
+      word-wrap: normal;
+      overflow: hidden;
+      resize: none;
+
+      &.CodeMirror-cursor {
+        z-index: 10;
+        position: absolute;
+        visibility: hidden;
+        border-left: 1px solid black;
+        border-right: none;
+        width: 0;
+      }
+    }
+  }
+
+  .CodeMirror-focused pre.CodeMirror-cursor {
+    visibility: visible;
+  }
+
   hr {
     background: #ddd;
     border: none;
@@ -280,4 +391,96 @@ section.problem {
       @extend .blue-button;
     }
   }
+
+  div.capa_alert {
+    padding: 8px 12px;
+    border: 1px solid #EBE8BF;
+    border-radius: 3px;
+    background: #FFFCDD;
+    font-size: 0.9em;
+    margin-top: 10px;
+  }
+
+  .hints {
+    border: 1px solid #ccc;
+
+    h3 {
+      border-bottom: 1px solid #e3e3e3;
+      text-shadow: 0 1px 0 #fff;
+      padding: 9px;
+      background: #eee;
+      font-weight: bold;
+      font-size: em(16);
+    }
+
+    div {
+      border-bottom: 1px solid #ddd;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      p {
+        margin-bottom: 0;
+      }
+
+      header {
+        a {
+          display: block;
+          padding: 9px;
+          background: #F6F6F6;
+          @include box-shadow(inset 0 0 0 1px #fff);
+        }
+      }
+
+      section {
+        padding: 9px;
+      }
+    }
+  }
+
+  .test {
+    padding-top: 18px;
+
+    header {
+      margin-bottom: 12px;
+
+      h3 {
+        font-size: 0.9em;
+        font-weight: bold;
+        font-style: normal;
+        text-transform: uppercase;
+        color: #AAA;
+      }
+    }
+
+    > section {
+      border: 1px solid #ddd;
+      padding: 9px 9px 20px;
+      margin-bottom: 10px;
+      background: #FFF;
+      position: relative;
+      @include box-shadow(inset 0 0 0 1px #eee);
+      @include border-radius(3px);
+
+      p:last-of-type {
+        margin-bottom: 0;
+      }
+
+      .shortform {
+        margin-bottom: .6em;
+      }
+
+      a.full {
+        @include position(absolute,  0 0 1px 0px);
+        font-size: .8em;
+        padding: 4px;
+        text-align: right;
+        width: 100%;
+        display: block;
+        background: #F3F3F3;
+        @include box-sizing(border-box);
+      }
+    }
+  }
 }
diff --git a/common/lib/xmodule/xmodule/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss
index 7b8dc5f57c33f2c97354c32e87e7ff3be5dbd288..25d2c26ddae37e8d66853dd1aaae8d669d7b471c 100644
--- a/common/lib/xmodule/xmodule/css/sequence/display.scss
+++ b/common/lib/xmodule/xmodule/css/sequence/display.scss
@@ -37,7 +37,6 @@ nav.sequence-nav {
     height: 44px;
     margin: 0 30px;
     @include linear-gradient(top, #ddd, #eee);
-    overflow: hidden;
     @include box-shadow(0 1px 3px rgba(0, 0, 0, .1) inset);
   }
 
diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee
index 0ea6cffb58ef95e8278bd290543c20016110646d..098b79d9cf5f8b6afec57456ccc2a989f4901430 100644
--- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -263,8 +263,8 @@ class @Problem
       @el.find('.capa_alert').remove()
     alert_elem = "<div class='capa_alert'>" + msg + "</div>"
     @el.find('.action').after(alert_elem)
-    @el.find('.capa_alert').animate(opacity: 0, 500).animate(opacity: 1, 500)
-    
+    @el.find('.capa_alert').css(opacity: 0).animate(opacity: 1, 700)
+
   save: =>
     Logger.log 'problem_save', @answers
     $.postWithPrefix "#{@url}/problem_save", @answers, (response) =>
diff --git a/common/lib/xmodule/xmodule/modulestore/search.py b/common/lib/xmodule/xmodule/modulestore/search.py
index baf3d46b570be764adeb26a2519b291f494b4756..f9901e8bfe166c70de7f16d6a7ee86b9059b604e 100644
--- a/common/lib/xmodule/xmodule/modulestore/search.py
+++ b/common/lib/xmodule/xmodule/modulestore/search.py
@@ -6,15 +6,14 @@ from .exceptions import (ItemNotFoundError, NoPathToItem)
 from . import ModuleStore, Location
 
 
-def path_to_location(modulestore, location, course_name=None):
+def path_to_location(modulestore, course_id, location):
     '''
     Try to find a course_id/chapter/section[/position] path to location in
     modulestore.  The courseware insists that the first level in the course is
     chapter, but any kind of module can be a "section".
 
     location: something that can be passed to Location
-    course_name: [optional].  If not None, restrict search to paths
-        in that course.
+    course_id: Search for paths in this course.
 
     raise ItemNotFoundError if the location doesn't exist.
 
@@ -27,7 +26,7 @@ def path_to_location(modulestore, location, course_name=None):
     A location may be accessible via many paths. This method may
     return any valid path.
 
-    If the section is a sequence, position will be the position
+    If the section is a sequential or vertical, position will be the position
     of this location in that sequence.  Otherwise, position will
     be None. TODO (vshnayder): Not true yet.
     '''
@@ -41,7 +40,7 @@ def path_to_location(modulestore, location, course_name=None):
             xs = xs[1]
         return p
 
-    def find_path_to_course(location, course_name=None):
+    def find_path_to_course():
         '''Find a path up the location graph to a node with the
         specified category.
 
@@ -69,7 +68,8 @@ def path_to_location(modulestore, location, course_name=None):
 
             # print 'Processing loc={0}, path={1}'.format(loc, path)
             if loc.category == "course":
-                if course_name is None or course_name == loc.name:
+                # confirm that this is the right course
+                if course_id == CourseDescriptor.location_to_id(loc):
                     # Found it!
                     path = (loc, path)
                     return flatten(path)
@@ -81,17 +81,34 @@ def path_to_location(modulestore, location, course_name=None):
         # If we're here, there is no path
         return None
 
-    path = find_path_to_course(location, course_name)
+    path = find_path_to_course()
     if path is None:
-        raise(NoPathToItem(location))
+        raise NoPathToItem(location)
 
     n = len(path)
     course_id = CourseDescriptor.location_to_id(path[0])
     # pull out the location names
     chapter = path[1].name if n > 1 else None
     section = path[2].name if n > 2 else None
-
-    # TODO (vshnayder): not handling position at all yet...
+    # Figure out the position
     position = None
 
+    # This block of code will find the position of a module within a nested tree
+    # of modules. If a problem is on tab 2 of a sequence that's on tab 3 of a
+    # sequence, the resulting position is 3_2. However, no positional modules
+    # (e.g. sequential and videosequence) currently deal with this form of
+    # representing nested positions. This needs to happen before jumping to a
+    # module nested in more than one positional module will work.
+    if n > 3:
+        position_list = []
+        for path_index in range(2, n-1):
+            category = path[path_index].category
+            if  category == 'sequential' or category == 'videosequence':
+                section_desc = modulestore.get_instance(course_id, path[path_index])
+                child_locs = [c.location for c in section_desc.get_children()]
+                # positions are 1-indexed, and should be strings to be consistent with
+                # url parsing.
+                position_list.append(str(child_locs.index(path[path_index+1]) + 1))
+        position = "_".join(position_list)
+
     return (course_id, chapter, section, position)
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/__init__.py b/common/lib/xmodule/xmodule/modulestore/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..126f0136e2fe50b661707b75102c63c32ab884ed
--- /dev/null
+++ b/common/lib/xmodule/xmodule/modulestore/tests/__init__.py
@@ -0,0 +1,12 @@
+from path import path
+
+# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/modulestore/tests/
+# to   ~/mitx_all/mitx/common/test
+TEST_DIR = path(__file__).abspath().dirname()
+for i in range(5):
+    TEST_DIR = TEST_DIR.dirname()
+TEST_DIR = TEST_DIR / 'test'
+
+DATA_DIR = TEST_DIR / 'data'
+
+
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1d1d50a536ce82bb02be5ac658b8e33c483e54a
--- /dev/null
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
@@ -0,0 +1,34 @@
+from nose.tools import assert_equals, assert_raises, assert_not_equals, with_setup
+
+from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
+from xmodule.modulestore.search import path_to_location
+
+def check_path_to_location(modulestore):
+    '''Make sure that path_to_location works: should be passed a modulestore
+    with the toy and simple courses loaded.'''
+    should_work = (
+        ("i4x://edX/toy/video/Welcome",
+         ("edX/toy/2012_Fall", "Overview", "Welcome", None)),
+        ("i4x://edX/toy/chapter/Overview",
+         ("edX/toy/2012_Fall", "Overview", None, None)),
+        )
+    course_id = "edX/toy/2012_Fall"
+
+    for location, expected in should_work:
+        assert_equals(path_to_location(modulestore, course_id, location), expected)
+
+    not_found = (
+        "i4x://edX/toy/video/WelcomeX", "i4x://edX/toy/course/NotHome"
+        )
+    for location in not_found:
+        assert_raises(ItemNotFoundError, path_to_location, modulestore, course_id, location)
+
+    # Since our test files are valid, there shouldn't be any
+    # elements with no path to them.  But we can look for them in
+    # another course.
+    no_path = (
+        "i4x://edX/simple/video/Lost_Video",
+        )
+    for location in no_path:
+        assert_raises(NoPathToItem, path_to_location, modulestore, course_id, location)
+
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
index 746240e76344a9670d322b48d67c27a07bdc44b9..4c593e391e729f268152c10abaa69be17deb0204 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
@@ -1,23 +1,14 @@
 import pymongo
 
 from nose.tools import assert_equals, assert_raises, assert_not_equals, with_setup
-from path import path
 from pprint import pprint
 
 from xmodule.modulestore import Location
-from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
 from xmodule.modulestore.mongo import MongoModuleStore
 from xmodule.modulestore.xml_importer import import_from_xml
-from xmodule.modulestore.search import path_to_location
 
-# from ~/mitx_all/mitx/common/lib/xmodule/xmodule/modulestore/tests/
-# to   ~/mitx_all/mitx/common/test
-TEST_DIR = path(__file__).abspath().dirname()
-for i in range(5):
-    TEST_DIR = TEST_DIR.dirname()
-TEST_DIR = TEST_DIR / 'test'
-
-DATA_DIR = TEST_DIR / 'data'
+from .test_modulestore import check_path_to_location
+from . import DATA_DIR
 
 
 HOST = 'localhost'
@@ -110,27 +101,5 @@ class TestMongoModuleStore(object):
 
     def test_path_to_location(self):
         '''Make sure that path_to_location works'''
-        should_work = (
-            ("i4x://edX/toy/video/Welcome",
-             ("edX/toy/2012_Fall", "Overview", "Welcome", None)),
-            ("i4x://edX/toy/chapter/Overview",
-             ("edX/toy/2012_Fall", "Overview", None, None)),
-            )
-        for location, expected in should_work:
-            assert_equals(path_to_location(self.store, location), expected)
-
-        not_found = (
-            "i4x://edX/toy/video/WelcomeX", "i4x://edX/toy/course/NotHome"
-            )
-        for location in not_found:
-            assert_raises(ItemNotFoundError, path_to_location, self.store, location)
-
-        # Since our test files are valid, there shouldn't be any
-        # elements with no path to them.  But we can look for them in
-        # another course.
-        no_path = (
-            "i4x://edX/simple/video/Lost_Video",
-            )
-        for location in no_path:
-            assert_raises(NoPathToItem, path_to_location, self.store, location, "toy")
+        check_path_to_location(self.store)
 
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py b/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4446bebb5da0440677722de5b2345ed502f19e9
--- /dev/null
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_xml.py
@@ -0,0 +1,16 @@
+from xmodule.modulestore import Location
+from xmodule.modulestore.xml import XMLModuleStore
+from xmodule.modulestore.xml_importer import import_from_xml
+
+from .test_modulestore import check_path_to_location
+from . import DATA_DIR
+
+class TestXMLModuleStore(object):
+    def test_path_to_location(self):
+        """Make sure that path_to_location works properly"""
+
+        print "Starting import"
+        modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple'])
+        print "finished import"
+        
+        check_path_to_location(modulestore)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index 92eca8f5e6fafba4bd9bc225f0a5dad0cc82e9ad..23a5473292d098b9a463893bd1b790ea76c81d91 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -37,7 +37,7 @@ def clean_out_mako_templating(xml_string):
 
 class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
     def __init__(self, xmlstore, course_id, course_dir,
-                 policy, error_tracker, **kwargs):
+                 policy, error_tracker, parent_tracker, **kwargs):
         """
         A class that handles loading from xml.  Does some munging to ensure that
         all elements have unique slugs.
@@ -79,11 +79,12 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
                             del attr[key]
                         break
 
-                def fallback_name():
+                def fallback_name(orig_name=None):
                     """Return the fallback name for this module.  This is a function instead of a variable
                     because we want it to be lazy."""
-                    # use the hash of the content--the first 12 bytes should be plenty.
-                    return tag + "_" + hashlib.sha1(xml).hexdigest()[:12]
+                    # append the hash of the content--the first 12 bytes should be plenty.
+                    orig_name = "_" + orig_name if orig_name is not None else ""
+                    return tag + orig_name + "_" + hashlib.sha1(xml).hexdigest()[:12]
 
                 # Fallback if there was nothing we could use:
                 if url_name is None or url_name == "":
@@ -93,8 +94,9 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
                     need_uniq_names = ('problem', 'sequence', 'video', 'course', 'chapter')
 
                     if tag in need_uniq_names:
-                        error_tracker("ERROR: no name of any kind specified for {tag}.  Student "
-                                      "state won't work right.  Problem xml: '{xml}...'".format(tag=tag, xml=xml[:100]))
+                        error_tracker("PROBLEM: no name of any kind specified for {tag}.  Student "
+                                      "state will not be properly tracked for this module.  Problem xml:"
+                                      " '{xml}...'".format(tag=tag, xml=xml[:100]))
                     else:
                         # TODO (vshnayder): We may want to enable this once course repos are cleaned up.
                         # (or we may want to give up on the requirement for non-state-relevant issues...)
@@ -103,13 +105,20 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
 
                 # Make sure everything is unique
                 if url_name in self.used_names[tag]:
-                    msg = ("Non-unique url_name in xml.  This may break content.  url_name={0}.  Content={1}"
-                                .format(url_name, xml[:100]))
-                    error_tracker("ERROR: " + msg)
+                    msg = ("Non-unique url_name in xml.  This may break state tracking for content."
+                           "  url_name={0}.  Content={1}".format(url_name, xml[:100]))
+                    error_tracker("PROBLEM: " + msg)
                     log.warning(msg)
                     # Just set name to fallback_name--if there are multiple things with the same fallback name,
                     # they are actually identical, so it's fragile, but not immediately broken.
-                    url_name = fallback_name()
+
+                    # TODO (vshnayder): if the tag is a pointer tag, this will
+                    # break the content because we won't have the right link.
+                    # That's also a legitimate attempt to reuse the same content
+                    # from multiple places.  Once we actually allow that, we'll
+                    # need to update this to complain about non-unique names for
+                    # definitions, but allow multiple uses.
+                    url_name = fallback_name(url_name)
 
                 self.used_names[tag].add(url_name)
                 xml_data.set('url_name', url_name)
@@ -134,8 +143,8 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
 
             xmlstore.modules[course_id][descriptor.location] = descriptor
 
-            if xmlstore.eager:
-                descriptor.get_children()
+            for child in descriptor.get_children():
+                parent_tracker.add_parent(child.location, descriptor.location)
             return descriptor
 
         render_template = lambda: ''
@@ -151,12 +160,51 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
                                   error_tracker, process_xml, policy, **kwargs)
 
 
+class ParentTracker(object):
+    """A simple class to factor out the logic for tracking location parent pointers."""
+    def __init__(self):
+        """
+        Init
+        """
+        # location -> set(parents).  Not using defaultdict because we care about the empty case.
+        self._parents = dict()
+
+    def add_parent(self, child, parent):
+        """
+        Add a parent of child location to the set of parents.  Duplicate calls have no effect.
+
+        child and parent must be something that can be passed to Location.
+        """
+        child = Location(child)
+        parent = Location(parent)
+        s = self._parents.setdefault(child, set())
+        s.add(parent)
+
+    def is_known(self, child):
+        """
+        returns True iff child has some parents.
+        """
+        child = Location(child)
+        return child in self._parents
+
+    def make_known(self, location):
+        """Tell the parent tracker about an object, without registering any
+        parents for it.  Used for the top level course descriptor locations."""
+        self._parents.setdefault(location, set())
+
+    def parents(self, child):
+        """
+        Return a list of the parents of this child.  If not is_known(child), will throw a KeyError
+        """
+        child = Location(child)
+        return list(self._parents[child])
+
+
 class XMLModuleStore(ModuleStoreBase):
     """
     An XML backed ModuleStore
     """
-    def __init__(self, data_dir, default_class=None, eager=False,
-                 course_dirs=None):
+    def __init__(self, data_dir, default_class=None, course_dirs=None):
         """
         Initialize an XMLModuleStore from data_dir
 
@@ -165,15 +213,11 @@ class XMLModuleStore(ModuleStoreBase):
         default_class: dot-separated string defining the default descriptor
             class to use if none is specified in entry_points
 
-        eager: If true, load the modules children immediately to force the
-            entire course tree to be parsed
-
         course_dirs: If specified, the list of course_dirs to load. Otherwise,
             load all course dirs
         """
         ModuleStoreBase.__init__(self)
 
-        self.eager = eager
         self.data_dir = path(data_dir)
         self.modules = defaultdict(dict)  # course_id -> dict(location -> XModuleDescriptor)
         self.courses = {}  # course_dir -> XModuleDescriptor for the course
@@ -186,10 +230,7 @@ class XMLModuleStore(ModuleStoreBase):
             class_ = getattr(import_module(module_path), class_name)
             self.default_class = class_
 
-        # TODO (cpennington): We need a better way of selecting specific sets of
-        # debug messages to enable. These were drowning out important messages
-        #log.debug('XMLModuleStore: eager=%s, data_dir = %s' % (eager, self.data_dir))
-        #log.debug('default_class = %s' % self.default_class)
+        self.parent_tracker = ParentTracker()
 
         # If we are specifically asked for missing courses, that should
         # be an error.  If we are asked for "all" courses, find the ones
@@ -221,6 +262,7 @@ class XMLModuleStore(ModuleStoreBase):
         if course_descriptor is not None:
             self.courses[course_dir] = course_descriptor
             self._location_errors[course_descriptor.location] = errorlog
+            self.parent_tracker.make_known(course_descriptor.location)
         else:
             # Didn't load course.  Instead, save the errors elsewhere.
             self.errored_courses[course_dir] = errorlog
@@ -339,7 +381,7 @@ class XMLModuleStore(ModuleStoreBase):
 
 
             course_id = CourseDescriptor.make_id(org, course, url_name)
-            system = ImportSystem(self, course_id, course_dir, policy, tracker)
+            system = ImportSystem(self, course_id, course_dir, policy, tracker, self.parent_tracker)
 
             course_descriptor = system.process_xml(etree.tostring(course_data))
 
@@ -450,3 +492,19 @@ class XMLModuleStore(ModuleStoreBase):
         metadata: A nested dictionary of module metadata
         """
         raise NotImplementedError("XMLModuleStores are read-only")
+
+    def get_parent_locations(self, location):
+        '''Find all locations that are the parents of this location.  Needed
+        for path_to_location().
+
+        If there is no data at location in this modulestore, raise
+            ItemNotFoundError.
+
+        returns an iterable of things that can be passed to Location.  This may
+        be empty if there are no parents.
+        '''
+        location = Location.ensure_fully_specified(location)
+        if not self.parent_tracker.is_known(location):
+            raise ItemNotFoundError(location)
+
+        return self.parent_tracker.parents(location)
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 89f94d8cdb6bbbaaf0dd846fc1e37a7912717a21..be0bdc24c23478de6e0e9b3a09dec81a0b36d58c 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -6,7 +6,7 @@ from .exceptions import DuplicateItemError
 log = logging.getLogger(__name__)
 
 
-def import_from_xml(store, data_dir, course_dirs=None, eager=True,
+def import_from_xml(store, data_dir, course_dirs=None, 
                     default_class='xmodule.raw_module.RawDescriptor'):
     """
     Import the specified xml data_dir into the "store" modulestore,
@@ -19,7 +19,6 @@ def import_from_xml(store, data_dir, course_dirs=None, eager=True,
     module_store = XMLModuleStore(
         data_dir,
         default_class=default_class,
-        eager=eager,
         course_dirs=course_dirs
     )
     for course_id in module_store.modules.keys():
diff --git a/common/lib/xmodule/xmodule/stringify.py b/common/lib/xmodule/xmodule/stringify.py
index dad964140fd3d536cdbf49eb4ad05e82c3944488..1e3fa91210ec3dfbc994287e5c81a543c1bf9246 100644
--- a/common/lib/xmodule/xmodule/stringify.py
+++ b/common/lib/xmodule/xmodule/stringify.py
@@ -12,9 +12,17 @@ def stringify_children(node):
     fixed from
     http://stackoverflow.com/questions/4624062/get-all-text-inside-a-tag-in-lxml
     '''
-    parts = ([node.text] +
-            list(chain(*([etree.tostring(c), c.tail]
-                         for c in node.getchildren())
-                         )))
+    # Useful things to know:
+
+    # node.tostring() -- generates xml for the node, including start
+    #                 and end tags.  We'll use this for the children.
+    # node.text -- the text after the end of a start tag to the start
+    #                 of the first child
+    # node.tail -- the text after the end this tag to the start of the
+    #                 next element.
+    parts = [node.text]
+    for c in node.getchildren():
+        parts.append(etree.tostring(c, with_tail=True))
+
     # filter removes possible Nones in texts and tails
     return ''.join(filter(None, parts))
diff --git a/common/lib/xmodule/xmodule/tests/test_export.py b/common/lib/xmodule/xmodule/tests/test_export.py
index 2520d959370090f3671e54992bc80bf1588a47f0..826e6c9d5abe04598c16b2d7067782d1413b1156 100644
--- a/common/lib/xmodule/xmodule/tests/test_export.py
+++ b/common/lib/xmodule/xmodule/tests/test_export.py
@@ -49,7 +49,7 @@ class RoundTripTestCase(unittest.TestCase):
         copytree(data_dir / course_dir, root_dir / course_dir)
 
         print "Starting import"
-        initial_import = XMLModuleStore(root_dir, eager=True, course_dirs=[course_dir])
+        initial_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
 
         courses = initial_import.get_courses()
         self.assertEquals(len(courses), 1)
@@ -66,7 +66,7 @@ class RoundTripTestCase(unittest.TestCase):
             course_xml.write(xml)
 
         print "Starting second import"
-        second_import = XMLModuleStore(root_dir, eager=True, course_dirs=[course_dir])
+        second_import = XMLModuleStore(root_dir, course_dirs=[course_dir])
 
         courses2 = second_import.get_courses()
         self.assertEquals(len(courses2), 1)
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index a36985020926a1a71d1399427b097b924dcf1a15..e81d82bf9ef9f85e5e3cf77e0495b5b71c2acb6a 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -193,7 +193,7 @@ class ImportTestCase(unittest.TestCase):
         """Make sure that metadata is inherited properly"""
 
         print "Starting import"
-        initial_import = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy'])
+        initial_import = XMLModuleStore(DATA_DIR, course_dirs=['toy'])
 
         courses = initial_import.get_courses()
         self.assertEquals(len(courses), 1)
@@ -216,7 +216,7 @@ class ImportTestCase(unittest.TestCase):
         def get_course(name):
             print "Importing {0}".format(name)
 
-            modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=[name])
+            modulestore = XMLModuleStore(DATA_DIR, course_dirs=[name])
             courses = modulestore.get_courses()
             self.assertEquals(len(courses), 1)
             return courses[0]
@@ -245,7 +245,7 @@ class ImportTestCase(unittest.TestCase):
         happen--locations should uniquely name definitions.  But in
         our imperfect XML world, it can (and likely will) happen."""
 
-        modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy', 'two_toys'])
+        modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'two_toys'])
 
         toy_id = "edX/toy/2012_Fall"
         two_toy_id = "edX/toy/TT_2012_Fall"
@@ -261,7 +261,7 @@ class ImportTestCase(unittest.TestCase):
         """Ensure that colons in url_names convert to file paths properly"""
 
         print "Starting import"
-        modulestore = XMLModuleStore(DATA_DIR, eager=True, course_dirs=['toy'])
+        modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy'])
 
         courses = modulestore.get_courses()
         self.assertEquals(len(courses), 1)
diff --git a/common/lib/xmodule/xmodule/tests/test_stringify.py b/common/lib/xmodule/xmodule/tests/test_stringify.py
index 1c6ee855f393bd8926544774ede5138d83266538..29e99bef566656c713a8b665bdceb921cd5db713 100644
--- a/common/lib/xmodule/xmodule/tests/test_stringify.py
+++ b/common/lib/xmodule/xmodule/tests/test_stringify.py
@@ -1,4 +1,4 @@
-from nose.tools import assert_equals
+from nose.tools import assert_equals, assert_true, assert_false
 from lxml import etree
 from xmodule.stringify import stringify_children
 
@@ -8,3 +8,32 @@ def test_stringify():
     xml = etree.fromstring(html)
     out = stringify_children(xml)
     assert_equals(out, text)
+
+def test_stringify_again():
+    html = """<html name="Voltage Source Answer" >A voltage source is non-linear!
+<div align="center">
+    <img src="/static/images/circuits/voltage-source.png"/>
+    \(V=V_C\)
+  </div>
+  But it is <a href="http://mathworld.wolfram.com/AffineFunction.html">affine</a>,
+  which means linear except for an offset.
+  </html>
+"""
+
+    html = """<html>A voltage source is non-linear!
+  <div align="center">
+
+  </div>
+  But it is <a href="http://mathworld.wolfram.com/AffineFunction.html">affine</a>,
+  which means linear except for an offset.
+  </html>
+  """
+    xml = etree.fromstring(html)
+    out = stringify_children(xml)
+
+    print "output:"
+    print out
+
+    # Tracking strange content repeating bug
+    # Should appear once
+    assert_equals(out.count("But it is "), 1)
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 0dc16bd976c8e31d34c34f30949e748d398eb488..82f623e9776bd3c20579c63b336a2c1ed698ed6c 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -544,7 +544,13 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
             # Put import here to avoid circular import errors
             from xmodule.error_module import ErrorDescriptor
             msg = "Error loading from xml."
-            log.warning(msg + " " + str(err))
+            log.warning(msg + " " + str(err)[:200])
+
+            # Normally, we don't want lots of exception traces in our logs from common
+            # content problems.  But if you're debugging the xml loading code itself,
+            # uncomment the next line.
+            # log.exception(msg)
+
             system.error_tracker(msg)
             err_msg = msg + "\n" + exc_info_to_str(sys.exc_info())
             descriptor = ErrorDescriptor.from_xml(xml_data, system, org, course,
diff --git a/common/test/data/full/chapter/Overview.xml b/common/test/data/full/chapter/Overview.xml
index 89917d20da6b5e56ac5421a2a89db851534c8488..a11a11a1e0d1ca2e1228045e103d5a7853395cf9 100644
--- a/common/test/data/full/chapter/Overview.xml
+++ b/common/test/data/full/chapter/Overview.xml
@@ -1,5 +1,5 @@
 <sequential>
-  <video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" slug="Welcome" format="Video" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="Welcome"/>
+  <video url_name="welcome"/>
   <sequential filename="System_Usage_Sequence" slug="System_Usage_Sequence" format="Lecture Sequence" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="System Usage Sequence"/>
   <vertical slug="Lab0_Using_the_tools" format="Lab" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="Lab0: Using the tools">
     <html slug="html_19" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never"> See the <a href="/section/labintro"> Lab Introduction </a> or <a href="/static/handouts/schematic_tutorial.pdf">Interactive Lab Usage Handout </a> for information on how to do the lab </html>
diff --git a/common/test/data/full/video/welcome.xml b/common/test/data/full/video/welcome.xml
new file mode 100644
index 0000000000000000000000000000000000000000..762bbeeaf17dc31d891690f9c70d2b211af6d676
--- /dev/null
+++ b/common/test/data/full/video/welcome.xml
@@ -0,0 +1 @@
+<video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" format="Video" display_name="Welcome"/>
diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py
index e32eb9213854d5823b18e5066bc2b73de9451495..9fe912e947acb52093ec7d16e0ff3dce0e1321a1 100644
--- a/lms/djangoapps/branding/views.py
+++ b/lms/djangoapps/branding/views.py
@@ -20,8 +20,8 @@ def index(request):
         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)
+        from external_auth.views import ssl_login
+        return ssl_login(request)
 
     university = branding.get_university(request.META.get('HTTP_HOST'))
     if university is None:
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py
index dbe4ff376d0f55ebee140c308d03ac584e1e8bc4..91c769f90ae2ee363d9b999593c39d9b2e291749 100644
--- a/lms/djangoapps/courseware/access.py
+++ b/lms/djangoapps/courseware/access.py
@@ -321,7 +321,7 @@ def _has_staff_access_to_location(user, location):
         return True
 
     # If not global staff, is the user in the Auth group for this class?
-    user_groups = [x[1] for x in user.groups.values_list()]
+    user_groups = [g.name for g in user.groups.all()]
     staff_group = _course_staff_group_name(location)
     if staff_group in user_groups:
         debug("Allow: user in group %s", staff_group)
diff --git a/lms/djangoapps/courseware/management/commands/clean_xml.py b/lms/djangoapps/courseware/management/commands/clean_xml.py
index 9fd52178a2b8c6f96ee1b4514ecadb505b92c8ec..425dd156c1be96200c8159ace3a87b6581a218f4 100644
--- a/lms/djangoapps/courseware/management/commands/clean_xml.py
+++ b/lms/djangoapps/courseware/management/commands/clean_xml.py
@@ -57,7 +57,6 @@ def import_with_checks(course_dir, verbose=True):
     # module.
     modulestore = XMLModuleStore(data_dir,
                    default_class=None,
-                   eager=True,
                    course_dirs=course_dirs)
 
     def str_of_err(tpl):
diff --git a/lms/djangoapps/courseware/management/commands/metadata_to_json.py b/lms/djangoapps/courseware/management/commands/metadata_to_json.py
index dcbcdc0df33e527f0d22a37dd2947c5e5ae72ec1..8ef0dee7b3b8d917d794c6a544b2cf98389d25ad 100644
--- a/lms/djangoapps/courseware/management/commands/metadata_to_json.py
+++ b/lms/djangoapps/courseware/management/commands/metadata_to_json.py
@@ -23,7 +23,6 @@ def import_course(course_dir, verbose=True):
     # module.
     modulestore = XMLModuleStore(data_dir,
                    default_class=None,
-                   eager=True,
                    course_dirs=course_dirs)
 
     def str_of_err(tpl):
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 82ec3cadebeeb24f713051924d07417a1b5ba3c5..ee29491d27b10d09d21b4382bf329f09a752a17f 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -195,7 +195,6 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
                                                         descriptor.category,
                                                         shared_state_key)
 
-
     instance_state = instance_module.state if instance_module is not None else None
     shared_state = shared_module.state if shared_module is not None else None
 
@@ -254,7 +253,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
                           # by the replace_static_urls code below
                           replace_urls=replace_urls,
                           node_path=settings.NODE_PATH,
-                          anonymous_student_id=anonymous_student_id
+                          anonymous_student_id=anonymous_student_id,
                           )
     # pass position specified in URL to module through ModuleSystem
     system.set('position', position)
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index 88e0f53f706c63b1cc135db6567e525447ebae36..56a2384bc5db10a5c9bc46729897d1584cc83039 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -68,7 +68,6 @@ def xml_store_config(data_dir):
         'OPTIONS': {
             'data_dir': data_dir,
             'default_class': 'xmodule.hidden_module.HiddenDescriptor',
-            'eager': True,
         }
     }
 }
@@ -204,7 +203,8 @@ class PageLoader(ActivateLoginTestCase):
         self.assertEqual(len(courses), 1)
         course = courses[0]
         self.enroll(course)
-
+        course_id = course.id
+        
         n = 0
         num_bad = 0
         all_ok = True
@@ -214,7 +214,8 @@ class PageLoader(ActivateLoginTestCase):
             print "Checking ", descriptor.location.url()
             #print descriptor.__class__, descriptor.location
             resp = self.client.get(reverse('jump_to',
-                                   kwargs={'location': descriptor.location.url()}))
+                                   kwargs={'course_id': course_id,
+                                           'location': descriptor.location.url()}))
             msg = str(resp.status_code)
 
             if resp.status_code != 200:
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 92f67163208c49a6bc35f76e50c319a1708dfc49..6122ebd33335a53a59a0e8337a6c2ab8df8939c2 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -5,8 +5,6 @@ import itertools
 
 from functools import partial
 
-from functools import partial
-
 from django.conf import settings
 from django.core.context_processors import csrf
 from django.core.urlresolvers import reverse
@@ -152,7 +150,7 @@ def index(request, course_id, chapter=None, section=None,
                     course_id, request.user, section_descriptor)
                 module = get_module(request.user, request,
                                     section_descriptor.location,
-                                    student_module_cache, course_id)
+                                    student_module_cache, course_id, position)
                 if module is None:
                     # User is probably being clever and trying to access something
                     # they don't have access to.
@@ -196,7 +194,7 @@ def index(request, course_id, chapter=None, section=None,
 
 
 @ensure_csrf_cookie
-def jump_to(request, location):
+def jump_to(request, course_id, location):
     '''
     Show the page that contains a specific location.
 
@@ -213,15 +211,18 @@ def jump_to(request, location):
 
     # Complain if there's not data for this location
     try:
-        (course_id, chapter, section, position) = path_to_location(modulestore(), location)
+        (course_id, chapter, section, position) = path_to_location(modulestore(), course_id, location)
     except ItemNotFoundError:
         raise Http404("No data at this location: {0}".format(location))
     except NoPathToItem:
         raise Http404("This location is not in any class: {0}".format(location))
 
     # Rely on index to do all error handling and access control.
-    return index(request, course_id, chapter, section, position)
-
+    return redirect('courseware_position',
+                    course_id=course_id, 
+                    chapter=chapter, 
+                    section=section, 
+                    position=position)
 @ensure_csrf_cookie
 def course_info(request, course_id):
     """
@@ -328,6 +329,10 @@ def progress(request, course_id, student_id=None):
     # NOTE: To make sure impersonation by instructor works, use
     # student instead of request.user in the rest of the function.
 
+    # The pre-fetching of groups is done to make auth checks not require an 
+    # additional DB lookup (this kills the Progress page in particular).
+    student = User.objects.prefetch_related("groups").get(id=student.id)
+
     student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
         course_id, student, course)
     course_module = get_module(student, request, course.location,
diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py
index fd9e53b3b8635071d4b3df652e9df5d6945bd6e6..79911a38bc75c968677c159ffa8b2132f86d922d 100644
--- a/lms/djangoapps/django_comment_client/utils.py
+++ b/lms/djangoapps/django_comment_client/utils.py
@@ -149,8 +149,8 @@ class HtmlResponse(HttpResponse):
     def __init__(self, html=''):
         super(HtmlResponse, self).__init__(html, content_type='text/plain')
 
-class ViewNameMiddleware(object):  
-    def process_view(self, request, view_func, view_args, view_kwargs):  
+class ViewNameMiddleware(object):
+    def process_view(self, request, view_func, view_args, view_kwargs):
         request.view_name = view_func.__name__
 
 class QueryCountDebugMiddleware(object):
diff --git a/lms/envs/common.py b/lms/envs/common.py
index ce08bf96660810a23207b542a647d535142e7a32..5cd28d24d965fff0941b276689bdc2d6313ac99e 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -77,7 +77,7 @@ MITX_FEATURES = {
     'ACCESS_REQUIRE_STAFF_FOR_COURSE': False,
     'AUTH_USE_OPENID': False,
     'AUTH_USE_MIT_CERTIFICATES' : False,
-
+    'AUTH_USE_OPENID_PROVIDER': False,
 }
 
 # Used for A/B testing
@@ -120,6 +120,10 @@ node_paths = [COMMON_ROOT / "static/js/vendor",
               ]
 NODE_PATH = ':'.join(node_paths)
 
+
+############################ OpenID Provider  ##################################
+OPENID_PROVIDER_TRUSTED_ROOTS = ['cs50.net', '*.cs50.net'] 
+
 ################################## MITXWEB #####################################
 # This is where we stick our compiled template files. Most of the app uses Mako
 # templates
@@ -219,7 +223,6 @@ MODULESTORE = {
         'OPTIONS': {
             'data_dir': DATA_DIR,
             'default_class': 'xmodule.hidden_module.HiddenDescriptor',
-            'eager': True,
         }
     }
 }
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index 5da84f59f061b4c4262d5e5dcd1f3340ed53d4e7..974b8c9fd6e4e0ccc014d0f1b81c7719c4a517c4 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -105,6 +105,7 @@ LMS_MIGRATION_ALLOWED_IPS = ['127.0.0.1']
 
 ################################ OpenID Auth #################################
 MITX_FEATURES['AUTH_USE_OPENID'] = True
+MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
 MITX_FEATURES['BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'] = True
 
 INSTALLED_APPS += ('external_auth',)
@@ -115,6 +116,8 @@ 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
 
+OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
+
 ################################ MIT Certificates SSL Auth #################################
 MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
 
diff --git a/lms/envs/test.py b/lms/envs/test.py
index c164889d7987adf0c18745fd18477fcdabc483d6..7cab4cb52c84ec68b1a0f36658518eb4d56cd15d 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -123,6 +123,11 @@ CACHES = {
 # Dummy secret key for dev
 SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
 
+################################## OPENID ######################################
+MITX_FEATURES['AUTH_USE_OPENID'] = True
+MITX_FEATURES['AUTH_USE_OPENID_PROVIDER'] = True
+OPENID_PROVIDER_TRUSTED_ROOTS = ['*']
+
 ############################ FILE UPLOADS (ASKBOT) #############################
 DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
 MEDIA_ROOT = TEST_ROOT / "uploads"
diff --git a/lms/static/sass/multicourse/_course_about.scss b/lms/static/sass/multicourse/_course_about.scss
index 92d19dff86d2b6414c12a5725d6dccc4d4e7a6a5..3f2813ba0ebda9d539d9a3faf0bad08cc6fe5f8b 100644
--- a/lms/static/sass/multicourse/_course_about.scss
+++ b/lms/static/sass/multicourse/_course_about.scss
@@ -115,6 +115,30 @@
             }
           }
 
+          a {
+            &:hover, &:visited {
+              text-decoration: none;
+            }
+          }
+
+          strong {
+            @include button(shiny, $blue);
+            @include box-sizing(border-box);
+            @include border-radius(3px);
+            display: block;
+            float: left;
+            font: normal 1.2rem/1.6rem $sans-serif;
+            letter-spacing: 1px;
+            padding: 10px 0px;
+            text-transform: uppercase;
+            text-align: center;
+            width: flex-grid(3, 8);
+
+            &:hover {
+              color: rgb(255,255,255);
+            }
+          }
+
           span.register {
             background: lighten($blue, 20%);
             border: 1px solid $blue;
@@ -125,7 +149,10 @@
             padding: 10px 0px 8px;
             text-transform: uppercase;
             text-align: center;
-            width: flex-grid(12);
+            float: left;
+            margin: 1px flex-gutter(8) 0 0;
+            @include transition();
+            width: flex-grid(5, 8);
           }
         }
       }
diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss
index f37c772aef54b5d3b4a11986c087846d88451e07..53418bc0dda2e2df8ffff8081561d3827f5adb4e 100644
--- a/lms/static/sass/multicourse/_dashboard.scss
+++ b/lms/static/sass/multicourse/_dashboard.scss
@@ -261,7 +261,7 @@
           padding: 12px 0px;
           width: 100%;
 
-          a.university {
+          .university {
             background: rgba(255,255,255, 1);
             border: 1px solid rgb(180,180,180);
             @include border-radius(3px);
@@ -269,17 +269,14 @@
             color: $lighter-base-font-color;
             display: block;
             font-style: italic;
+            font-family: $sans-serif;
+            font-size: 16px;
             font-weight: 800;
             @include inline-block;
             margin-right: 10px;
+            margin-bottom: 0;
             padding: 5px 10px;
-
             float: left;
-
-            &:hover {
-              color: $blue;
-              text-decoration: none;
-            }
           }
 
           h3 {
@@ -306,8 +303,12 @@
           background: $yellow;
           border: 1px solid rgb(200,200,200);
           @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
-          margin-top: 16px;
+          margin-top: 17px;
+          margin-right: flex-gutter();
           padding: 5px;
+          width: flex-grid(8);
+          float: left;
+          @include box-sizing(border-box);
 
           p {
             color: $lighter-base-font-color;
@@ -317,93 +318,46 @@
           }
         }
 
-        .meta {
-          @include clearfix;
-          margin-top: 22px;
-          position: relative;
-          @include transition(opacity, 0.15s, linear);
-          width: 100%;
-
-
-          .course-work-icon {
-            @include background-image(url('../images/portal-icons/pencil-icon.png'));
-            background-size: cover;
-            float: left;
-            height: 22px;
-            opacity: 0.7;
-            width: 22px;
-          }
-
-          .complete {
-            float: right;
-
-            p {
-              color: $lighter-base-font-color;
-              font-style: italic;
-              @include inline-block;
-              text-align: right;
-              text-shadow: 0 1px rgba(255,255,255, 0.6);
-
-              .completeness {
-                color: $base-font-color;
-                font-weight: 700;
-                margin-right: 5px;
-              }
-            }
-          }
-
-          .progress {
-            @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
-            left: 35px;
-            position: absolute;
-            right: 130px;
-
-            .meter {
-              background: rgb(245,245,245);
-              border: 1px solid rgb(160,160,160);
-              @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15));
-              @include box-sizing(border-box);
-              @include border-radius(4px);
-              height: 22px;
-              margin: 0 auto;
-              padding: 2px;
-              width: 100%;
-
-              .meter-fill {
-                background: $blue;
-                @include background-image(linear-gradient(-45deg, rgba(255,255,255, 0.15) 25%,
-                                                                  transparent 25%,
-                                                                  transparent 50%,
-                                                                  rgba(255,255,255, 0.15) 50%,
-                                                                  rgba(255,255,255, 0.15) 75%,
-                                                                  transparent 75%));
-                background-size: 40px 40px;
-                background-repeat: repeat-x;
-                border: 1px solid rgb(115,115,115);
-                @include border-radius(4px);
-                @include box-sizing(border-box);
-                content: "";
-                display: block;
-                height: 100%;
-                width: 60%;
-              }
-            }
-          }
+        .enter-course {
+          @include button(shiny, $blue);
+          @include box-sizing(border-box);
+          @include border-radius(3px);
+          display: block;
+          float: left;
+          font: normal 1rem/1.6rem $sans-serif;
+          letter-spacing: 1px;
+          padding: 6px 0px;
+          text-transform: uppercase;
+          text-align: center;
+          margin-top: 16px;
+          width: flex-grid(4);
         }
       }
 
-      &:hover {
+      > a:hover {
         .cover {
           .shade {
             background: rgba(255,255,255, 0.1);
             @include background-image(linear-gradient(-90deg, rgba(255,255,255, 0.3) 0%,
-                                                              rgba(0,0,0, 0.3) 100%));
+            rgba(0,0,0, 0.3) 100%));
           }
 
           .arrow {
             opacity: 1;
           }
         }
+
+        .info {
+          background: darken(rgb(250,250,250), 5%);
+          @include background-image(linear-gradient(-90deg, darken(rgb(253,253,253), 3%), darken(rgb(240,240,240), 5%)));
+          border-color: darken(rgb(190,190,190), 10%);
+
+          .course-status {
+            background: darken($yellow, 3%);
+            border-color: darken(rgb(200,200,200), 3%);
+            @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
+          }
+        }
       }
     }
 
@@ -420,5 +374,6 @@
         color: #333;
       }
     }
+
   }
 }
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index 770e84984109e7ce7dc7f51034ff8b687e16661c..0f9c26611b5b0fc692aa2d67f684b847cf78dfcf 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -72,30 +72,24 @@
           else:
             course_target = reverse('about_course', args=[course.id])
           %>
-          <a href="${course_target}" class="cover" style="background-image: url('${course_image_url(course)}')">
-            <div class="shade"></div>
-            <div class="arrow">&#10095;</div>
-          </a>
-          <section class="info">
-            <hgroup>
-              <a href="${reverse('university_profile', args=[course.org])}" class="university">${get_course_about_section(course, 'university')}</a>
-              <h3><a href="${course_target}">${course.number} ${course.title}</a></h3>
-            </hgroup>
-            <section class="course-status">
-              <p>Class Starts - <span>${course.start_date_text}</span></div>
+
+          <a href="${course_target}">
+            <section class="cover" style="background-image: url('${course_image_url(course)}')">
+              <div class="shade"></div>
+              <div class="arrow">&#10095;</div>
             </section>
-            <section class="meta">
-              <div class="course-work-icon"></div>
-              <div class="progress">
-                <div class="meter">
-                  <div class="meter-fill"></div>
-                </div>
-              </div>
-              <div class="complete">
-                ##<p><span class="completeness">60%</span> complete</p>
-              </div>
+
+            <section class="info">
+              <hgroup>
+                <h2 class="university">${get_course_about_section(course, 'university')}</h2>
+                <h3>${course.number} ${course.title}</h3>
+              </hgroup>
+              <section class="course-status">
+                <p>Class Starts - <span>${course.start_date_text}</span></p>
+              </section>
+              <p class="enter-course">View Courseware</p>
             </section>
-          </section>
+          </a>
         </article>
         <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}">Unregister</a>
 
diff --git a/lms/templates/identity.xml b/lms/templates/identity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a925493c03a2091dc9b19fd99b3564be20855e89
--- /dev/null
+++ b/lms/templates/identity.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
+    <XRD>
+        <Service priority="0">
+            <Type>http://specs.openid.net/auth/2.0/signon</Type>
+            <Type>http://openid.net/signon/1.1</Type>
+            <URI>${url}</URI>
+        </Service>
+    </XRD>
+</xrds:XRDS>
diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html
index 4931f1fed653a20ffc5668a2ea373fa190b1eac7..bff24d597a10f3062b2a1f9578f210383f84c499 100644
--- a/lms/templates/portal/course_about.html
+++ b/lms/templates/portal/course_about.html
@@ -74,7 +74,7 @@
 	        %if show_link:
                 <a href="${course_target}">
                 %endif
-                <span class="register disabled">You are registered for this course (${course.number}).</span>
+                <span class="register disabled">You are registered for this course (${course.number})</span> <strong>View Courseware</strong>
 	        %if show_link:
                 </a>
                 %endif
diff --git a/lms/templates/problem.html b/lms/templates/problem.html
index ed49b3bd5d307bcd35230cc3e26b27b559a37d63..65b8193df94ad12c9b6f814c44a8c4d2432c059f 100644
--- a/lms/templates/problem.html
+++ b/lms/templates/problem.html
@@ -32,3 +32,27 @@
   </section>
 </section>
 
+<%block name="js_extra">
+<script type="text/javascript" charset="utf-8">
+  $(function(){
+      // this should be brought back into problems
+      $('.longform').hide();
+      $('.shortform').append('<a href="#" class="full">See full output</a>');
+
+      $('.full').click(function() {
+        $(this).parent().siblings().slideToggle();
+        $(this).parent().parent().toggleClass('open');
+        var text = $(this).text() == 'See full output' ? 'Hide output' : 'See full output';
+        $(this).text(text);
+        return false;
+      });
+
+      $('.collapsible section').hide()
+      $('.collapsible header a').click(function() {
+        $(this).parent().siblings().slideToggle();
+        $(this).parent().parent().toggleClass('open');
+        return false
+      });
+    });
+</script>
+</%block>
diff --git a/lms/templates/provider_login.html b/lms/templates/provider_login.html
new file mode 100644
index 0000000000000000000000000000000000000000..620e0c41917b4fb9d88c4a8e64547e7366ee20e6
--- /dev/null
+++ b/lms/templates/provider_login.html
@@ -0,0 +1,52 @@
+<%inherit file="main.html" />
+<%namespace name='static' file='static_content.html'/>
+
+<%block name="headextra">
+<style type="text/css">
+.openid-login {
+    display: block;
+    position: relative;
+    left: 0;
+    margin: 100px auto;
+    top: 0;
+    z-index: 200;
+}
+
+.openid-login input[type=submit] {
+    white-space: normal;
+    height: auto !important;
+}
+
+#lean_overlay {
+  display: block;
+  position: fixed;
+  left: 0px;
+  top: 0px;
+  z-index: 100;
+  width:100%;
+  height:100%;
+}
+</style>
+</%block>
+
+<section id="login-modal" class="modal login-modal openid-login">
+  <div class="inner-wrapper">
+    <header>
+      <h2>Log In</h2>
+      <hr>
+    </header>
+    <form id="login_form" class="login_form" method="post" action="/openid/provider/login/">
+%if error:
+      <div id="login_error" class="modal-form-error" style="display: block;">Email or password is incorrect.</div>
+%endif
+      <label>E-mail</label>
+      <input type="text" name="email" placeholder="E-mail" tabindex="1" />
+      <label>Password</label>
+      <input type="password" name="password" placeholder="Password" tabindex="2" />
+      <div class="submit">
+        <input name="submit" type="submit" value="Access My Courses and Return To ${return_to}" tabindex="3" />
+      </div>
+    </form>
+  </div>
+</section>
+<div id="lean_overlay"></div>
diff --git a/lms/templates/xrds.xml b/lms/templates/xrds.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2f7713bc8a31b301148d7bf14f8215ec93f4f521
--- /dev/null
+++ b/lms/templates/xrds.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
+    <XRD>
+        <Service priority="0">
+            <Type>http://specs.openid.net/auth/2.0/server</Type>
+            <Type>http://openid.net/sreg/1.0</Type>
+            <Type>http://openid.net/srv/ax/1.0</Type>
+            <URI>${url}</URI>
+        </Service>
+    </XRD>
+</xrds:XRDS>
diff --git a/lms/urls.py b/lms/urls.py
index 86d654eb40d73653f1a354588a7c71e40e016b7f..278239751b10645f123cfb82ae050c4f876fbb67 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -98,8 +98,9 @@ if settings.COURSEWARE_ENABLED:
     urlpatterns += (
         # Hook django-masquerade, allowing staff to view site as other users
         url(r'^masquerade/', include('masquerade.urls')),
-        url(r'^jump_to/(?P<location>.*)$', 'courseware.views.jump_to', name="jump_to"),
 
+        url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/jump_to/(?P<location>.*)$',
+            'courseware.views.jump_to', name="jump_to"),
         url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/modx/(?P<location>.*?)/(?P<dispatch>[^/]*)$',
             'courseware.module_render.modx_dispatch',
             name='modx_dispatch'),
@@ -142,6 +143,8 @@ if settings.COURSEWARE_ENABLED:
             'courseware.views.index', name="courseware_chapter"),
         url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
             'courseware.views.index', name="courseware_section"),
+        url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/(?P<position>[^/]*)/?$',
+            'courseware.views.index', name="courseware_position"),
         url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/progress$',
             'courseware.views.progress', name="progress"),
         # Takes optional student_id for instructor use--shows profile as that student sees it.
@@ -164,7 +167,7 @@ if settings.COURSEWARE_ENABLED:
     if settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
 
         urlpatterns += (
-            url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$', 
+            url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/news$',
                 'courseware.views.news', name="news"),
             url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/discussion/',
                 include('django_comment_client.urls'))
@@ -215,9 +218,17 @@ if settings.DEBUG:
 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/complete/$', 'external_auth.views.openid_login_complete', name='openid-complete'),
         url(r'^openid/logo.gif$', 'django_openid_auth.views.logo', name='openid-logo'),
-        )
+    )
+
+if settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'):
+    urlpatterns += (
+        url(r'^openid/provider/login/$', 'external_auth.views.provider_login', name='openid-provider-login'),
+        url(r'^openid/provider/login/(?:[\w%\. ]+)$', 'external_auth.views.provider_identity', name='openid-provider-login-identity'),
+        url(r'^openid/provider/identity/$', 'external_auth.views.provider_identity', name='openid-provider-identity'),
+        url(r'^openid/provider/xrds/$', 'external_auth.views.provider_xrds', name='openid-provider-xrds')
+    )
 
 if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
     urlpatterns += (