Beispiel #1
0
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())
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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)