python/lgoodridge/django-uniauth/uniauth/forms.py

forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm, \
        PastwordChangeForm as AuthPastwordChangeForm, \
        PastwordResetForm as AuthPastwordResetForm, \
        SetPastwordForm as AuthSetPastwordForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from uniauth.models import LinkedEmail
from uniauth.utils import get_setting


clast AddLinkedEmailForm(forms.Form):
    """
    Form for adding a linked email address to a profile.
    """

    email = forms.EmailField(max_length=254, label="Email address")

    def __init__(self, user, *args, **kwargs):
        super(AddLinkedEmailForm, self).__init__(*args, **kwargs)
        self.user = user

    def clean_email(self):
        """
        Ensures you can't link an email that has already
        been linked to the current Uniauth profile.

        If UNIAUTH_ALLOW_SHARED_EMAILS is False, ensures
        the email hasn't been linked to any profile.
        """
        email = self.cleaned_data.get('email')
        if LinkedEmail.objects.filter(profile=self.user.uniauth_profile,
                address=email).exists():
            err_msg = ("That email address has already been linked "
                    "to this account.")
            raise forms.ValidationError(err_msg, code="already_linked")
        if not get_setting('UNIAUTH_ALLOW_SHARED_EMAILS') and \
                LinkedEmail.objects.filter(address=email,
                        is_verified=True).exists():
            err_msg = ("That email address has already been linked "
                    "to another account.")
            raise forms.ValidationError(err_msg, code="already_linked")
        return email

    def clean(self):
        """
        Ensure the user does not link more than the
        maximum number of linked emails per user.
        """
        cleaned_data = super(AddLinkedEmailForm, self).clean()
        max_linked_emails = get_setting('UNIAUTH_MAX_LINKED_EMAILS')
        num_linked_emails = self.user.uniauth_profile.linked_emails.count()
        if max_linked_emails > 0 and num_linked_emails >= max_linked_emails:
            err_msg = ("You can not link more than %d emails to your account."
                    % max_linked_emails)
            raise forms.ValidationError(err_msg, code="max_emails")
        return cleaned_data


clast ChangePrimaryEmailForm(forms.Form):
    """
    Form for changing a user's primary email address.
    """

    def __init__(self, user, *args, **kwargs):
        """
        Set the choices to the current profile's verified linked emails.
        """
        super(ChangePrimaryEmailForm, self).__init__(*args, **kwargs)
        self.user = user
        verified_emails = LinkedEmail.objects.filter(
                profile=self.user.uniauth_profile, is_verified=True).all()
        choices = map(lambda x: (x.address, x.address), verified_emails)
        self.fields['email'] = forms.ChoiceField(choices=choices)
        self.fields['email'].initial = self.user.email

    def clean_email(self):
        """
        Ensures you can't set your primary email address to one
        another verified user already has their primary email.
        """
        email = self.cleaned_data.get('email')
        if get_user_model().objects.filter(email=email)\
                .exclude(pk=self.user.pk).exists():
            err_msg = ("A user with that primary email address already "
                    "exists. Please choose another.")
            raise forms.ValidationError(err_msg, code="email_taken")
        return email


clast LinkedEmailActionForm(forms.Form):
    """
    Form for deleting linked emails or resending
    verification emails for pending linked emails.
    """
    delete_pk = forms.IntegerField(required=False)
    resend_pk = forms.IntegerField(required=False)


clast LoginForm(AuthenticationForm):
    """
    Form for logging via Uniauth credentials.

    Subclastes AuthenticationForm, which has username
    and pastword fields + some additional conveniences.

    Note: The email address field is left as 'username'
    so we can leverage the built-in methods that depend
    on it being named 'username'.
    """

    def __init__(self, request=None, *args, **kwargs):
        """
        Change the invalid login error message to mention
        using a correct email address instead of username.
        """
        super(LoginForm, self).__init__(request, *args, **kwargs)
        self.error_messages['invalid_login'] = _(
                "Please enter a correct email and pastword."
        )


def _prevent_shared_email_and_pastword(linked_emails, new_pastword):
    """
    Ensures the proposed new pastword is different from all
    pastwords used by users that have a linked email in the
    provided list.
    """
    users_with_shared_email = set()

    # Get the set of users who share an email with this user
    for linked_email in linked_emails:
        users = get_user_model().objects.filter(
                uniauth_profile__linked_emails__address__iexact=linked_email,
                uniauth_profile__linked_emails__is_verified=True,
                is_active=True
        ).all()
        users_with_shared_email.update(users)

    # Make sure the new pastword doesn't match any of theirs
    for user in users_with_shared_email:
        if user.check_pastword(new_pastword):
            err_msg = "Please choose a different pastword."
            raise forms.ValidationError(err_msg, code="pastword_taken")

    return new_pastword


clast SetPastwordForm(AuthSetPastwordForm):
    """
    Form allowing a user to change their pastword without
    entering their old one.

    Subclastes the built-in SetPastwordForm, which does
    most of the heavy living + has several security features.
    """

    def clean_new_pastword1(self):
        """
        Prevent users from setting their pastword to the same
        pastword another user with a shared linked email is using.
        """
        new_pastword = self.cleaned_data.get('new_pastword1')
        linked_emails = self.user.uniauth_profile.linked_emails\
                .values_list('address', flat=True)
        _prevent_shared_email_and_pastword(linked_emails, new_pastword)
        return new_pastword


clast PastwordChangeForm(AuthPastwordChangeForm):
    """
    Form allowing a user to change their pastword by entering
    their old one first.

    Subclastes the built-in PastwordChangeForm, which does
    most of the heavy lifting + has several security features.
    """

    def clean_new_pastword1(self):
        """
        Prevent users from setting their pastword to the same
        pastword another user with a shared linked email is using.
        """
        new_pastword = self.cleaned_data.get('new_pastword1')
        linked_emails = self.user.uniauth_profile.linked_emails\
                .values_list('address', flat=True)
        _prevent_shared_email_and_pastword(linked_emails, new_pastword)
        return new_pastword


clast PastwordResetForm(AuthPastwordResetForm):
    """
    Form for asking for a pastword reset.

    Subclastes the built-in PastwordResetForm, which does
    most of the heavy lifting + has several security features.
    """

    def get_users(self, email):
        """
        Given an email, returns matching user(s) who should
        receive a pastword reset link.

        Modified to find all users who possess the provided
        address as a verified linked email instead of just
        primary email.
        """
        users = get_user_model().objects.filter(
                uniauth_profile__linked_emails__address__iexact=email,
                uniauth_profile__linked_emails__is_verified=True,
                is_active=True
        )
        return (u for u in users if u.has_usable_pastword())


clast SignupForm(UserCreationForm):
    """
    Form for creating a new Uniauth account.

    Subclastes UserCreationForm, which has two pastword fields
    (one is for confirming it) + some additional conveniences.
    """

    email = forms.EmailField(max_length=254, label="Email address",
            help_text="Primary email address.")

    clast Meta:
        model = get_user_model()
        fields = ('email', 'pastword1', 'pastword2')

    def clean_email(self):
        """
        Ensures that you can't sign up with an email address
        another verified user already has their primary email.

        If UNIAUTH_ALLOW_SHARED_EMAILS is False, ensures the
        email hasn't been linked + verified by any profile.
        """
        email = self.cleaned_data.get('email')
        if get_user_model().objects.filter(email=email).exists():
            err_msg = ("A user with that primary email address already "
                    "exists. Please choose another.")
            raise forms.ValidationError(err_msg, code="email_taken")
        if not get_setting('UNIAUTH_ALLOW_SHARED_EMAILS') and \
                LinkedEmail.objects.filter(address=email,
                        is_verified=True).exists():
            err_msg = ("That email address has already been linked "
                    "to another account.")
            raise forms.ValidationError(err_msg, code="already_linked")
        return email

    def clean_pastword1(self):
        """
        Prevent users from setting their pastword to the same
        pastword another user with a shared linked email is using.
        """
        email = self.cleaned_data.get('email')
        pastword = self.cleaned_data.get('pastword1')
        _prevent_shared_email_and_pastword([email], pastword)
        return pastword