Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new authentication backend for case-insensitive emails #31

Merged
merged 8 commits into from
Apr 2, 2015
32 changes: 32 additions & 0 deletions authtools/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.contrib.auth.backends import ModelBackend


class CaseInsensitiveEmailBackendMixin(object):
def authenticate(self, username=None, password=None, **kwargs):
"""
This authentication backend assumes that usernames are email addresses and simply
lowercases a username before an attempt is made to authenticate said username using a
superclass's authenticate method. This superclass should be either a user-defined
authentication backend, or a Django-provided authentication backend (e.g., ModelBackend).

Example usage:
See CaseInsensitiveEmailModelBackend, below.

NOTE:
A word of caution. Use of this backend presupposes a way to ensure that users cannot
create usernames that differ only in case (e.g., [email protected] and [email protected]). It is
advised that you use this backend in conjunction with the
EmailInsensitiveCreateUserForm provided in the forms module.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nitpicky, but it would be nice to just provide the qualname for the form:

authtools.forms.EmailInsensitiveCreateUserForm

It's just so that I don't have to parse "the forms module"

"""
if username is not None:
username = username.lower()

return super(CaseInsensitiveEmailBackendMixin, self).authenticate(
username=username,
password=password,
**kwargs
)


class CaseInsensitiveEmailModelBackend(CaseInsensitiveEmailBackendMixin, ModelBackend):
pass
14 changes: 14 additions & 0 deletions authtools/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ def save(self, commit=True):
return user


class CaseInsensitiveEmailUserCreationForm(UserCreationForm):
"""
This form is the same as UserCreationForm, except that usernames are lowercased before they
are saved. This is to disallow the existence of email address uernames which differ only in
case.
"""
def clean_username(self):
username = self.cleaned_data.get('username')
if username:
username = username.lower()

return username


class UserChangeForm(forms.ModelForm):
"""
A form for updating users. Includes all the fields on
Expand Down
23 changes: 23 additions & 0 deletions docs/backends.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Authentication Backends
=====

.. currentmodule:: authtools.backends

django-authtools provides two authorization backend classes. These backends offer more customization
for how your :class:`authtool.models.User` class is authenticated.

.. class:: CaseInsensitiveEmailBackendMixin

This mixin simply calls the ``authenticate`` method of its superclass after lowercasing the
provided username. This superclass should be a user-defined or Django-provided authentication
backend, such as ``django.contrib.auth.backends.ModelBackend``.

.. warning:
Use of this mixin presupposes that all usernames are stored in their lowercase form, and
that there is no way to have usernames differing only in case. If usernames can differ in
case, this authentication backend mixin could cause errors in user authentication.

.. class:: CaseInsensitiveEmailModelBackend
A subclass of the ``CaseInsentiveEmailBackendMixin`` with
``django.contrib.auth.backends.ModelBackend`` as its chosen authentication backend superclass.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a sentence like: to use this, put this in your settings:

AUTHENTICATION_BACKENDS = (
    'authtools.backends.CaseInsensitiveEmailModelBackend',
)

And then mention that if they have any other authentication backends that they need to mix the mixin into them.


6 changes: 6 additions & 0 deletions docs/forms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ User model that follows the :class:`User class contract <django:django.contrib.a
Basically the same as django.contrib.auth, but respects ``USERNAME_FIELD``
and ``User.REQUIRED_FIELDS``.

.. class:: CaseInsensitiveEmailUserCreationForm
This is the same form as ``UserCreationForm``, but with an added method, ``clean_username``
which lowercases the username before saving. It is recommended that you use this form if you
choose to use either the ``CaseInsensitiveEmailBackendMixin`` or
``CaseInsensitiveEmailModelBackend`` authentication backend classes.

.. class:: UserChangeForm

A normal ModelForm that adds a ``ReadOnlyPasswordHashField`` with the
Expand Down