From c367c8a8ced8ff3d9ef1a894fc9ce522790c2e77 Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Wed, 25 Mar 2015 14:01:04 -0600 Subject: [PATCH 1/8] Add a new authentication backend for case-insensitive emails as a possible solution for issue #11 --- authtools/backends.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 authtools/backends.py diff --git a/authtools/backends.py b/authtools/backends.py new file mode 100644 index 0000000..930a247 --- /dev/null +++ b/authtools/backends.py @@ -0,0 +1,19 @@ +from django.contrib.auth.backends import ModelBackend + + +class CaseInsensitiveEmailBackend(ModelBackend): + """ + This authentication backend assumes that usernames are email addresses and simply lowercases + a username before an attempt is made to authenticate said username using Django's ModelBackend. + + Example usage: + # In settings.py + AUTHENTICATION_BACKENDS = ('authtools.backends.CaseInsensitiveEmailBackend',) + """ + def authenticate(self, username=None, password=None): + username = username.lower() + + return super(CaseInsensitiveEmailBackend, self).authenticate( + username=username, + password=password + ) From 2353bcb0fe05b9e2ced28f65f235d9746d54f64b Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Wed, 25 Mar 2015 14:30:16 -0600 Subject: [PATCH 2/8] Add **kwargs to method signature, check to see if username is passed in --- authtools/backends.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/authtools/backends.py b/authtools/backends.py index 930a247..382649c 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -10,10 +10,12 @@ class CaseInsensitiveEmailBackend(ModelBackend): # In settings.py AUTHENTICATION_BACKENDS = ('authtools.backends.CaseInsensitiveEmailBackend',) """ - def authenticate(self, username=None, password=None): - username = username.lower() + def authenticate(self, username=None, password=None, **kwargs): + if username is not None: + username = username.lower() return super(CaseInsensitiveEmailBackend, self).authenticate( username=username, - password=password + password=password, + **kwargs ) From 999786e8027cd39bc1ee29e13614f3e84c80769e Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Thu, 26 Mar 2015 08:25:07 -0600 Subject: [PATCH 3/8] Add cautionary message to potential users of the backend --- authtools/backends.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/authtools/backends.py b/authtools/backends.py index 382649c..6e8b4e9 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -9,6 +9,11 @@ class CaseInsensitiveEmailBackend(ModelBackend): Example usage: # In settings.py AUTHENTICATION_BACKENDS = ('authtools.backends.CaseInsensitiveEmailBackend',) + + 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). Using this backend + in such a system is a huge security risk. """ def authenticate(self, username=None, password=None, **kwargs): if username is not None: From 23ec0899eaf60a9dc79f6671461a33eea7e7f464 Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Thu, 2 Apr 2015 09:22:09 -0600 Subject: [PATCH 4/8] Add mixin to make the case-insensitive email auth backend more flexible --- authtools/backends.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/authtools/backends.py b/authtools/backends.py index 6e8b4e9..8df5e9f 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -1,6 +1,18 @@ from django.contrib.auth.backends import ModelBackend +class CaseInsensitiveEmailBackendMixin(object): + def authenticate(self, username=None, password=None, **kwargs): + if username is not None: + username = username.lower() + + return super(CaseInsensitiveEmailBackendMixin, self).authenticate( + username=username, + password=password, + **kwargs + ) + + class CaseInsensitiveEmailBackend(ModelBackend): """ This authentication backend assumes that usernames are email addresses and simply lowercases From 50aa976efc7bedfcf5705cb7b98ef504aa3e2e25 Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Thu, 2 Apr 2015 09:46:25 -0600 Subject: [PATCH 5/8] Move docstring to mixin, and remove redundant functionality in CaseInsensitiveEmailModelBackend --- authtools/backends.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/authtools/backends.py b/authtools/backends.py index 8df5e9f..40109dc 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -3,6 +3,21 @@ 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 + EmailInsensitiveCreateUserForm provided in the forms module. + """ if username is not None: username = username.lower() @@ -13,26 +28,5 @@ def authenticate(self, username=None, password=None, **kwargs): ) -class CaseInsensitiveEmailBackend(ModelBackend): - """ - This authentication backend assumes that usernames are email addresses and simply lowercases - a username before an attempt is made to authenticate said username using Django's ModelBackend. - - Example usage: - # In settings.py - AUTHENTICATION_BACKENDS = ('authtools.backends.CaseInsensitiveEmailBackend',) - - 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). Using this backend - in such a system is a huge security risk. - """ - def authenticate(self, username=None, password=None, **kwargs): - if username is not None: - username = username.lower() - - return super(CaseInsensitiveEmailBackend, self).authenticate( - username=username, - password=password, - **kwargs - ) +class CaseInsensitiveEmailModelBackend(CaseInsensitiveEmailBackendMixin, ModelBackend): + pass From 70f8804577036c07fac625e7d3c7b5f4f4c0b762 Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Thu, 2 Apr 2015 09:46:43 -0600 Subject: [PATCH 6/8] Add docs for new authentication backend classes --- docs/backends.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/backends.rst diff --git a/docs/backends.rst b/docs/backends.rst new file mode 100644 index 0000000..5632c1c --- /dev/null +++ b/docs/backends.rst @@ -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. + From bfd7dd635ec5cb606cfc7796397c0d260f0b0a80 Mon Sep 17 00:00:00 2001 From: Bradley Gordon Date: Thu, 2 Apr 2015 09:48:24 -0600 Subject: [PATCH 7/8] Add new UserCreationForm subclass and corresponding documentation --- authtools/forms.py | 14 ++++++++++++++ docs/forms.rst | 6 ++++++ 2 files changed, 20 insertions(+) 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/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 Date: Thu, 2 Apr 2015 09:52:29 -0600 Subject: [PATCH 8/8] Add suggestion in backends docs, fix typo in backends.py --- authtools/backends.py | 2 +- docs/backends.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/authtools/backends.py b/authtools/backends.py index 40109dc..ea40065 100644 --- a/authtools/backends.py +++ b/authtools/backends.py @@ -16,7 +16,7 @@ def authenticate(self, username=None, password=None, **kwargs): 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 - EmailInsensitiveCreateUserForm provided in the forms module. + CaseInsensitiveEmailUserCreationForm provided in the forms module. """ if username is not None: username = username.lower() diff --git a/docs/backends.rst b/docs/backends.rst index 5632c1c..0203570 100644 --- a/docs/backends.rst +++ b/docs/backends.rst @@ -15,7 +15,9 @@ for how your :class:`authtool.models.User` class is authenticated. .. 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. + 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