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 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 verify_token(request, pk_base64, token): """ Verifies a token generated for validating an email address, and notifies the user whether verification was successful. """ next_url = request.GET.get('next') or request.GET.get(REDIRECT_FIELD_NAME) context = {'next_url': next_url, 'is_signup': False} user_model = get_user_model() # Attempt to get the linked email to verify try: email_pk = decode_pk(pk_base64) email = LinkedEmail.objects.get(pk=email_pk) except (TypeError, ValueError, OverflowError, user_model.DoesNotExist, LinkedEmail.DoesNotExist): email = None # In the unlikely scenario that a user is trying to sign up # with an email another verified user has as a primary email # address, reject verification immediately if email is not None and is_tmp_user(email.profile.user) and \ get_user_model().objects.filter(email=email.address).exists(): email = None # If the token successfully verifies, update the linked email if email is not None and token_generator.check_token(email, token): email.is_verified = True email.save() # If the user this email is linked to is a temporary # one, change it to a fully registered user user = email.profile.user if is_tmp_user(user) or is_unlinked_account(user): context['is_signup'] = True old_username = user.username # Change the email + username to the verified email user.email = email.address user.username = choose_username(user.email) user.save() # If the user was created via CAS, add the institution # account described by the temporary username if old_username.startswith("cas"): username_split = get_account_username_split(old_username) _add_institution_account(user.profile, username_split[1], username_split[2]) # If UNIAUTH_ALLOW_SHARED_EMAILS is False, and there were # pending LinkedEmails for this address on other accounts, # delete them if not get_setting('UNIAUTH_ALLOW_SHARED_EMAILS'): LinkedEmail.objects.filter(address=email.address, is_verified=False).delete() return render(request, 'uniauth/verification-success.html', context) # If anything went wrong, just render the failed verification template else: return render(request, 'uniauth/verification-failure.html', context)
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 settings(request): """ Allows the user to link additional emails, change the primary email address of, and link additional institution accounts to their Uniauth profile. """ context = _get_global_context(request) context['email_resent'] = None context['email_added'] = None context['password_changed'] = False action_email_form = None add_email_form = None change_email_form = None change_password_form = None # This page may only be accessed by users with Uniauth profiles: # if the user is logged in with an unlinked InsitutionAccount, # redirect them to the link page if is_unlinked_account(request.user): params = urlencode({'next': reverse('uniauth:settings')}) return HttpResponseRedirect(reverse('uniauth:link-to-profile') + \ '?' + params) # If it's a POST request, determine which form was submitted if request.method == 'POST': # Linked Email Action Form submitted: validate the # delete / resend action + perform it if able if request.POST.get('action-email-submitted'): action_email_form = LinkedEmailActionForm(request.POST) if action_email_form.is_valid(): delete_pk = action_email_form.cleaned_data.get("delete_pk") resend_pk = action_email_form.cleaned_data.get("resend_pk") # If they asked to delete a linked email: it must # belong to the user and not be the primary address if delete_pk: try: email = LinkedEmail.objects.get(pk=delete_pk) if email.profile.user == request.user and \ email.address != request.user.email: email.delete() except LinkedEmail.DoesNotExist: pass # If they asked to resend a verification email: it # must belong to the user and be pending verification elif resend_pk: try: email = LinkedEmail.objects.get(pk=resend_pk) if email.profile.user == request.user and \ not email.is_verified: _send_verification_email(request, email.address, email) context['email_resent'] = email.address except LinkedEmail.DoesNotExist: pass action_email_form = None # Add Linked Email Form submitted: validate and create # new linked email + send verification email elif request.POST.get('add-email-submitted'): add_email_form = AddLinkedEmailForm(request.user, request.POST) if add_email_form.is_valid(): email = LinkedEmail.objects.create( profile=request.user.profile, address=add_email_form.cleaned_data["email"], is_verified=False) _send_verification_email(request, email.address, email) context['email_added'] = email.address add_email_form = None # Change Primary Email Address Form submitted: # validate and set user's primary email address elif request.POST.get('change-email-submitted'): change_email_form = ChangePrimaryEmailForm(request.user, request.POST) if change_email_form.is_valid(): request.user.email = change_email_form.cleaned_data["email"] request.user.save() change_email_form = None # Change Password Form submitted: validate and set password elif request.POST.get('change-password-submitted'): change_password_form = PasswordChangeForm(request.user, request.POST) if change_password_form.is_valid(): change_password_form.save() update_session_auth_hash(request, request.user) context['password_changed'] = True change_password_form = None # Create blank versions of all non-bound forms if action_email_form is None: action_email_form = LinkedEmailActionForm(request.user) if add_email_form is None: add_email_form = AddLinkedEmailForm(request.user) if change_email_form is None: change_email_form = ChangePrimaryEmailForm(request.user) if change_password_form is None: change_password_form = PasswordChangeForm(request.user) # Assemble the context and render context['action_email_form'] = action_email_form context['add_email_form'] = add_email_form context['change_email_form'] = change_email_form context['change_password_form'] = change_password_form return render(request, 'uniauth/settings.html', context)