def test_get_redirect_url_defaults(self): """ Ensure method uses the provided default URL or default login URL setting if no other options are available """ request = self.factory.get("/accounts/login/", HTTP_REFERER="/home") result = get_redirect_url(request, use_referer=False, default_url="/default/") self.assertEqual(result, "/default/") request = self.factory.get("/accounts/login/", HTTP_REFERER="/home") result = get_redirect_url(request) default_login = DEFAULT_SETTING_VALUES["UNIAUTH_LOGIN_REDIRECT_URL"] self.assertEqual(result, default_login)
def signup(request): """ Creates a new Uniauth profile with the provided primary email address and password. Prompts user to verify the email address before profile is fully created. """ next_url = request.GET.get('next') context = _get_global_context(request) if not next_url: next_url = get_redirect_url(request) # If the user is already authenticated + has a Uniauth # profile, proceed to next page if request.user.is_authenticated and not is_tmp_user(request.user) \ and not is_unlinked_account(request.user): return HttpResponseRedirect(next_url) # If it's a POST request, attempt to validate the form if request.method == "POST": form = SignupForm(request.POST) # Validation successful: setup temporary user if form.is_valid(): form_email = form.cleaned_data['email'] user = request.user # If the user is not already authenticated, create a User if not user or not user.is_authenticated: tmp_username = get_random_username() user_model = get_user_model() user = user_model.objects.create(username=tmp_username) # Set user's password + create linked email user.set_password(form.cleaned_data["password1"]) user.save() email, _ = LinkedEmail.objects.get_or_create( profile=user.uniauth_profile, address=form_email, is_verified=False) # Send verification email + render waiting template _send_verification_email(request, email.address, email) return render(request, 'uniauth/verification-waiting.html', { 'email': email.address, 'is_signup': True }) # Validation failed: render form errors else: context['form'] = form return render(request, 'uniauth/signup.html', context) # Otherwise, render a blank Signup form else: form = SignupForm() context['form'] = form return render(request, 'uniauth/signup.html', context)
def test_get_redirect_url_query_parameter(self): """ Ensure method returns URL provided as query parameter over everything else if present """ request = self.factory.get("/accounts/login/", data={"next": "/next-page/"}) result = get_redirect_url(request) self.assertEqual(result, "/next-page/")
def test_get_redirect_url_referer(self): """ Ensure method returns the referring page if use_referer is True, and no relevant query parameter was provided """ request = self.factory.get("/accounts/cas-login/test-inst/", HTTP_REFERER="/home/") result = get_redirect_url(request, use_referer=True, default_url="/default/") self.assertEqual(result, "/home/")
def link_from_profile(request, institution): """ Attempts to authenticate a CAS account for the provided institution, and links it to the current Uniauth profile if successful. """ next_url = request.GET.get('next') ticket = request.GET.get('ticket') # Ensure there is an institution with the provided slug try: institution = Institution.objects.get(slug=institution) except Institution.DoesNotExist: raise Http404 if not next_url: next_url = get_redirect_url(request, use_referer=True) # If the user is not already logged into a verified # Uniauth account, raise permission denied if not request.user.is_authenticated or is_tmp_user(request.user) \ or is_unlinked_account(request.user): raise PermissionDenied("Must be logged in as verified Uniauth user.") service_url = get_service_url(request, next_url) client = CASClient(version=2, service_url=service_url, server_url=institution.cas_server_url) # If a ticket was provided, attempt to authenticate with it if ticket: user = authenticate(request=request, institution=institution, ticket=ticket, service=service_url) # Authentication successful: link to Uniauth profile if # the institution account has not been linked yet + proceed if user: if is_unlinked_account(user): merge_model_instances(request.user, [user]) username_split = get_account_username_split(user.username) _add_institution_account(request.user.profile, username_split[1], username_split[2]) return HttpResponseRedirect(next_url) # Authentication failed: raise permission denied else: raise PermissionDenied("Verification of CAS ticket failed") # If no ticket was provided, redirect to the # login URL for the institution's CAS server else: return HttpResponseRedirect(client.get_login_url())
def cas_login(request, institution): """ Redirects to the CAS login URL, or verifies the CAS ticket, if provided. Accepts the slug of the institution to log in to. """ next_url = request.GET.get('next') ticket = request.GET.get('ticket') # Ensure there is an institution with the provided slug try: institution = Institution.objects.get(slug=institution) except Institution.DoesNotExist: raise Http404 if not next_url: next_url = get_redirect_url(request, use_referer=True) # If the user is already authenticated, proceed to next page if request.user.is_authenticated: return _login_success(request, request.user, next_url) service_url = get_service_url(request, next_url) client = CASClient(version=2, service_url=service_url, server_url=institution.cas_server_url) # If a ticket was provided, attempt to authenticate with it if ticket: user = authenticate(request=request, institution=institution, ticket=ticket, service=service_url) # Authentication successful: setup session + proceed if user: if not request.session.exists(request.session.session_key): request.session.create() auth_login(request, user) request.session['auth-method'] = "cas-" + institution.slug return _login_success(request, user, next_url, ["ticket"]) # Authentication failed: raise permission denied else: raise PermissionDenied("Verification of CAS ticket failed.") # If no ticket was provided, redirect to the # login URL for the institution's CAS server else: return HttpResponseRedirect(client.get_login_url())
def logout(request): """ Logs the user out of their Uniauth account, and redirects to the next page, defaulting to the URL specified by the UNIAUTH_LOGOUT_REDIRECT_URL setting. If no redirect page is set (URL parameter not given and UNIAUTH_LOGOUT_REDIRECT_URL is None), renders the logout template. Also logs the user out of CAS if they logged in via CAS, and the UNIAUTH_LOGOUT_CAS_COMPLETELY setting is true. """ next_page = request.GET.get('next') auth_method = request.session.get('auth-method') if not next_page and get_setting('UNIAUTH_LOGOUT_REDIRECT_URL'): next_page = get_redirect_url( request, get_setting('UNIAUTH_LOGOUT_REDIRECT_URL')) # Formally log out user auth_logout(request) # Determine whether the user logged in through an institution's CAS institution = None if auth_method and auth_method.startswith("cas-"): try: institution = Institution.objects.get(slug=auth_method[4:]) except Institution.DoesNotExist: pass # If we need to logout an institution's CAS, # redirect to that CAS server's logout URL if institution and get_setting('UNIAUTH_LOGOUT_CAS_COMPLETELY'): redirect_url = urlunparse( (get_protocol(request), request.get_host(), next_page or reverse('uniauth:logout'), '', '', '')) client = CASClient(version=2, service_url=get_service_url(request), server_url=institution.cas_server_url) return HttpResponseRedirect(client.get_logout_url(redirect_url)) # If next page is set, proceed to it elif next_page: return HttpResponseRedirect(next_page) # Otherwise, render the logout view else: return render(request, 'uniauth/logout.html')
def link_to_profile(request): """ If the user is a temporary one who was logged in via an institution (not through a Uniauth profile), offers them the choice between logging to an existing Uniauth account or creating a new one. The institution account is (eventually) linked to the Uniauth profile the user logged into / created. """ next_url = request.GET.get('next') context = _get_global_context(request) if not next_url: next_url = get_redirect_url(request) params = urlencode({'next': next_url}) context['next_url'] = next_url # If the user is not authenticated at all, redirect to login page if not request.user.is_authenticated: return HttpResponseRedirect(reverse('uniauth:login') + '?' + params) # If the user is already authenticated + verified, proceed to next page if not is_tmp_user(request.user) and not is_unlinked_account(request.user): return HttpResponseRedirect(next_url) # If the user is temporary, but was not logged in via an institution # (e.g. created through Uniauth, but not verified), redirect to signup if not is_unlinked_account(request.user): return HttpResponseRedirect(reverse('uniauth:signup') + '?' + params) # At this point, we've ensured the user is temporary and was # logged in via an institution. We just need to handle the # Login Form, if the user chooses to link to an existing account. # If it's a POST request, attempt to validate the form if request.method == "POST": form = LoginForm(request, request.POST) # Authentication successful if form.is_valid(): unlinked_user = request.user username_split = get_account_username_split(request.user.username) # Log in as the authenticated Uniauth user user = form.get_user() auth_login(request, user) # Merge the unlinked account into the logged in profile, # then add the institution account described by the username merge_model_instances(user, [unlinked_user]) _add_institution_account(user.profile, username_split[1], username_split[2]) slug = username_split[1] context['institution'] = Institution.objects.get(slug=slug) return render(request, 'uniauth/link-success.html', context) # Authentication failed: render form errors else: context['form'] = form return render(request, 'uniauth/link-to-profile.html', context) # Otherwise, render a blank Login form else: form = LoginForm(request) context['form'] = form return render(request, 'uniauth/link-to-profile.html', context)
def login(request): """ Authenticates the user, then redirects them to the next page, defaulting to the URL specified by the UNIAUTH_LOGIN_REDIRECT_URL setting. Offers users the choice between logging in with their Uniauth credentials, or through the CAS interface for any supported institution. """ next_url = request.GET.get('next') context = _get_global_context(request) if not next_url: next_url = get_redirect_url(request) # If the user is already authenticated, proceed to next page if request.user.is_authenticated: return _login_success(request, request.user, next_url) display_standard = get_setting('UNIAUTH_LOGIN_DISPLAY_STANDARD') display_cas = get_setting('UNIAUTH_LOGIN_DISPLAY_CAS') num_institutions = len(context['institutions']) # Ensure the login settings are configured correctly if not display_standard and not display_cas: err_msg = "At least one of '%s' and '%s' must be True." % \ ('UNIAUTH_LOGIN_DISPLAY_STANDARD', 'UNIAUTH_LOGIN_DISPLAY_CAS') raise ImproperlyConfigured(err_msg) if display_cas and num_institutions == 0: err_msg = ("'%s' is True, but there are no Institutions in the " "database to log into!") % 'UNIAUTH_LOGIN_DISPLAY_CAS' raise ImproperlyConfigured(err_msg) context['display_standard'] = display_standard context['display_cas'] = display_cas context['num_institutions'] = num_institutions # If we aren't displaying the standard login form, # we're just displaying the CAS login options if not display_standard: institutions = context['institutions'] query_params = context['query_params'] # If there's only one possible institution to log # into, redirect to its CAS login page immediately if num_institutions == 1: return HttpResponseRedirect(institutions[0][2] + query_params) # Otherwise, render the page (without the Login form) else: return render(request, 'uniauth/login.html', context) # If we are displaying the login form, and it's # a POST request, attempt to validate the form elif request.method == "POST": form = LoginForm(request, request.POST) # Authentication successful: setup session + proceed if form.is_valid(): user = form.get_user() auth_login(request, user) request.session['auth-method'] = "uniauth" return _login_success(request, user, next_url) # Authentication failed: render form errors else: context['form'] = form return render(request, 'uniauth/login.html', context) # Otherwise, render a blank Login form else: form = LoginForm(request) context['form'] = form return render(request, 'uniauth/login.html', context)