diff --git a/authtools/backends.py b/authtools/backends.py new file mode 100644 index 0000000..ea40065 --- /dev/null +++ b/authtools/backends.py @@ -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., joe@test.org and JOE@test.org). It is + advised that you use this backend in conjunction with the + CaseInsensitiveEmailUserCreationForm provided in 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 diff --git a/authtools/forms.py b/authtools/forms.py index cb7e6f5..5139b2a 100644 --- a/authtools/forms.py +++ b/authtools/forms.py @@ -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 diff --git a/docs/backends.rst b/docs/backends.rst new file mode 100644 index 0000000..0203570 --- /dev/null +++ b/docs/backends.rst @@ -0,0 +1,25 @@ +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. It is + advised that you use this mixin in conjuction with the + ``CaseInsensitiveEmailUserCreationForm`` form provided in the ``forms`` module. + +.. class:: CaseInsensitiveEmailModelBackend + A subclass of the ``CaseInsentiveEmailBackendMixin`` with + ``django.contrib.auth.backends.ModelBackend`` as its chosen authentication backend superclass. + diff --git a/docs/forms.rst b/docs/forms.rst index b505d43..102117d 100644 --- a/docs/forms.rst +++ b/docs/forms.rst @@ -14,6 +14,12 @@ User model that follows the :class:`User class contract