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 = DjangoOpenIDStore() server = Server(store, endpoint) # first check to see if the request is an OpenID request. # If so, the client will have specified an 'openid.mode' as part # of the request. querydict = dict(request.REQUEST.items()) error = False if 'openid.mode' in request.GET or 'openid.mode' in request.POST: # decode request try: openid_request = server.decodeRequest(querydict) except (UntrustedReturnURL, ProtocolError): openid_request = None 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 # (by falling through to the provider_login at the # bottom of this method). 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 redirection: these are also sent to this view function, # but are distinguished by lacking the openid mode. We also know that # they are posts, because they come from the popup elif 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 # Failure is redirected to this method (by using the original URL), # which will bring up the login dialog. 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: %s" AUDIT_LOG.warning(msg, email) return HttpResponseRedirect(openid_request_url) # attempt to authenticate user (but not actually log them in...) # Failure is again redirected to the login dialog. username = user.username password = request.POST.get('password', None) try: user = authenticate(username=username, password=password, request=request) except RateLimitException: AUDIT_LOG.warning('OpenID - Too many failed login attempts.') return HttpResponseRedirect(openid_request_url) if user is None: request.session['openid_error'] = True msg = "OpenID login failed - password for %s is invalid" AUDIT_LOG.warning(msg, email) return HttpResponseRedirect(openid_request_url) # authentication succeeded, so fetch user information # that was requested 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'] AUDIT_LOG.info("OpenID login success - %s (%s)", user.username, user.email) # redirect user to return_to location url = endpoint + urlquote(user.username) response = openid_request.answer(True, None, url) # TODO: for CS50 we are forcibly returning the username # instead of fullname. In the OpenID simple registration # extension, we don't have to return any fields we don't # want to, even if they were marked as required by the # Consumer. The behavior of what to do when there are # missing fields is up to the Consumer. The proper change # should only return the username, however this will likely # break the CS50 client. Temporarily we will be returning # username filling in for fullname in addition to username # as sreg nickname. # Note too that this is hardcoded, and not really responding to # the extensions that were registered in the first place. results = { 'nickname': user.username, 'email': user.email, 'fullname': user.username } # the request succeeded: return provider_respond(server, openid_request, response, results) # the account is not active, so redirect back to the login page: request.session['openid_error'] = True msg = "Login failed - Account not active for user %s" AUDIT_LOG.warning(msg, username) 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 }) # add custom XRDS header necessary for discovery process response['X-XRDS-Location'] = get_xrds_url('xrds', request) return response
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 = DjangoOpenIDStore() server = Server(store, endpoint) # first check to see if the request is an OpenID request. # If so, the client will have specified an 'openid.mode' as part # of the request. querydict = dict(request.REQUEST.items()) error = False if 'openid.mode' in request.GET or 'openid.mode' in request.POST: # decode request try: openid_request = server.decodeRequest(querydict) except (UntrustedReturnURL, ProtocolError): openid_request = None 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 # (by falling through to the provider_login at the # bottom of this method). 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(), 'post_params': request.POST, } # 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 redirection: these are also sent to this view function, # but are distinguished by lacking the openid mode. We also know that # they are posts, because they come from the popup elif 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'] post_params = openid_setup['post_params'] # We need to preserve the parameters, and the easiest way to do this is # through the URL url_post_params = { param: post_params[param] for param in post_params if param.startswith('openid') } encoded_params = urllib.urlencode(url_post_params) if '?' not in openid_request_url: openid_request_url = openid_request_url + '?' + encoded_params else: openid_request_url = openid_request_url + '&' + encoded_params 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 # Failure is redirected to this method (by using the original URL), # which will bring up the login dialog. email = request.POST.get('email', None) try: user = User.objects.get(email=email) except User.DoesNotExist: request.session['openid_error'] = True if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.warning(u"OpenID login failed - Unknown user email") else: msg = u"OpenID login failed - Unknown user email: {0}".format( email) AUDIT_LOG.warning(msg) return HttpResponseRedirect(openid_request_url) # attempt to authenticate user (but not actually log them in...) # Failure is again redirected to the login dialog. username = user.username password = request.POST.get('password', None) try: user = authenticate(username=username, password=password, request=request) except RateLimitException: AUDIT_LOG.warning(u'OpenID - Too many failed login attempts.') return HttpResponseRedirect(openid_request_url) if user is None: request.session['openid_error'] = True if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.warning(u"OpenID login failed - invalid password") else: AUDIT_LOG.warning( u"OpenID login failed - password for %s is invalid", email) return HttpResponseRedirect(openid_request_url) # authentication succeeded, so fetch user information # that was requested 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'] if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.info(u"OpenID login success - user.id: %s", user.id) else: AUDIT_LOG.info(u"OpenID login success - %s (%s)", user.username, user.email) # redirect user to return_to location url = endpoint + urlquote(user.username) response = openid_request.answer(True, None, url) # Note too that this is hardcoded, and not really responding to # the extensions that were registered in the first place. results = { 'nickname': user.username, 'email': user.email, 'fullname': user.profile.name, } # the request succeeded: return provider_respond(server, openid_request, response, results) # the account is not active, so redirect back to the login page: request.session['openid_error'] = True if settings.FEATURES['SQUELCH_PII_IN_LOGS']: AUDIT_LOG.warning( u"Login failed - Account not active for user.id %s", user.id) else: AUDIT_LOG.warning(u"Login failed - Account not active for user %s", username) 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 }) # add custom XRDS header necessary for discovery process response['X-XRDS-Location'] = get_xrds_url('xrds', request) return response