Skip to content
This repository has been archived by the owner on Dec 6, 2021. It is now read-only.

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Feb 2, 2012
0 parents commit 735249c
Show file tree
Hide file tree
Showing 14 changed files with 618 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
*.pyc
MANIFEST
dist/
117 changes: 117 additions & 0 deletions README.md
@@ -0,0 +1,117 @@
Django Email as Username
========================

**User authentication with email addresses instead of usernames.**

**Author:** Tom Christie, [@_tomchristie][1].

**See also:** [django-email-login][2], [django-email-usernames][3].

Overview
========

Allows you to treat users as having only email addresses, instead of usernames.

1. Provides an email auth backend and helper functions for creating users.
2. Patches the Django admin to handle email based user authentication.
3. Overides the `createsuperuser` command to create users with email only.
4. Treats email authentication as case-insensitive.

Installation
============

Install from PyPI:

pip install django-email-as-username

Add 'emailusernames' to INSTALLED_APPS.

INSTALLED_APPS = (
...
'emailusernames',
)

Set `EmailAuthBackend` as your authentication backend:

AUTHENTICATION_BACKENDS = (
'emailusernames.backends.EmailAuthBackend',
)

Usage
=====

Creating users
--------------

You should create users using the `create_user` and `create_superuser`
functions.

from emailusernames.utils import create_user, create_superuser

create_user('me@example.com', 'password')
create_superuser('admin@example.com', 'password')

Retrieving users
----------------

You can retrieve users, using case-insensitive email matching, with the
`get_user` function. Similarly you can use `user_exists` to test if a given
user exists.

from emailusernames.utils import get_user, user_exists

user = get_user('someone@example.com')
...

if user_exists('someone@example.com'):
...

Updating users
--------------

You can update a user's email and save the instance, without having to also modify the username.

user.email = 'other@example.com'
user.save()

Note that the `user.username` attribute will always return the email address, but behind the scenes it will be stored as a hashed version of the user's email.

User Forms
----------

`emailusernames` provides the following several forms that you can use for authenticating, creating and updating users:

* `emailusernames.forms.EmailAuthenticationForm`
* `emailusernames.forms.EmailAdminAuthenticationForm`
* `emailusernames.forms.UserCreationForm`
* `emailusernames.forms.UserChangeForm`

License
=======

Copyright © 2012, DabApps.

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

[1]: http://twitter.com/_tomchristie
[2]: https://bitbucket.org/tino/django-email-login
[3]: https://bitbucket.org/hakanw/django-email-usernames
1 change: 1 addition & 0 deletions emailusernames/__init__.py
@@ -0,0 +1 @@
__version__ = '0.1.0'
36 changes: 36 additions & 0 deletions emailusernames/admin.py
@@ -0,0 +1,36 @@
"""
Override the add- and change-form in the admin, to hide the username.
"""
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.contrib import admin
from emailusernames.forms import EmailUserCreationForm, EmailUserChangeForm
from django.utils.translation import ugettext_lazy as _


class EmailLoginAdmin(UserAdmin):
add_form = EmailUserCreationForm
form = EmailUserChangeForm

add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Groups'), {'fields': ('groups',)}),
)
list_display = ('email', 'first_name', 'last_name', 'is_staff')


admin.site.unregister(User)
admin.site.register(User, EmailLoginAdmin)


def __email_unicode__(self):
return self.email
25 changes: 25 additions & 0 deletions emailusernames/backends.py
@@ -0,0 +1,25 @@
from django.contrib.auth.models import User
from emailusernames.utils import get_user


class EmailAuthBackend(object):

"""Allow users to log in with their email address"""

supports_inactive_user = False
supports_anonymous_user = False
supports_object_permissions = False

def authenticate(self, email=None, password=None):
try:
user = get_user(email)
if user.check_password(password):
return user
except User.DoesNotExist:
return None

def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
102 changes: 102 additions & 0 deletions emailusernames/forms.py
@@ -0,0 +1,102 @@
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm
from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from emailusernames.utils import user_exists


ERROR_MESSAGE = _("Please enter a correct email and password. ")
ERROR_MESSAGE_RESTRICTED = _("You do not have permission to access the admin.")
ERROR_MESSAGE_INACTIVE = _("This account is inactive.")


class EmailAuthenticationForm(AuthenticationForm):
"""
Override the default AuthenticationForm to force email-as-username behavior.
"""
email = forms.EmailField(label=_("Email"), max_length=70)
message_incorrect_password = ERROR_MESSAGE
message_inactive = ERROR_MESSAGE_INACTIVE

def __init__(self, *args, **kwargs):
super(EmailAdminAuthenticationForm, self).__init__(*args, **kwargs)
del self.fields['username']

def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')

if email and password:
self.user_cache = authenticate(email=email, password=password)
if (self.user_cache is None):
raise forms.ValidationError(self.message_incorrect_password)
if not self.user_cache.is_active:
raise forms.ValidationError(self.message_inactive)
self.check_for_test_cookie()
return self.cleaned_data


class EmailAdminAuthenticationForm(AdminAuthenticationForm):
"""
Override the default AuthenticationForm to force email-as-username behavior.
"""
email = forms.EmailField(label=_("Email"), max_length=70)
message_incorrect_password = ERROR_MESSAGE
message_inactive = ERROR_MESSAGE_INACTIVE
message_restricted = ERROR_MESSAGE_RESTRICTED

def __init__(self, *args, **kwargs):
super(EmailAdminAuthenticationForm, self).__init__(*args, **kwargs)
del self.fields['username']

def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')

if email and password:
self.user_cache = authenticate(email=email, password=password)
if (self.user_cache is None):
raise forms.ValidationError(self.message_incorrect_password)
if not self.user_cache.is_active:
raise forms.ValidationError(self.message_inactive)
if not self.user_cache.is_staff:
raise forms.ValidationError(self.message_restricted)
self.check_for_test_cookie()
return self.cleaned_data


class EmailUserCreationForm(UserCreationForm):
"""
Override the default UserCreationForm to force email-as-username behavior.
"""
email = forms.EmailField(label=_("Email"), max_length=70)

class Meta:
model = User
fields = ("email",)

def __init__(self, *args, **kwargs):
super(EmailUserCreationForm, self).__init__(*args, **kwargs)
del self.fields['username']

def clean_email(self):
email = self.cleaned_data["email"]
if user_exists(email):
raise forms.ValidationError(_("A user with that email already exists."))
return email


class EmailUserChangeForm(UserChangeForm):
"""
Override the default UserChangeForm to force email-as-username behavior.
"""
email = forms.EmailField(label=_("Email"), max_length=70)

class Meta:
model = User

def __init__(self, *args, **kwargs):
super(EmailUserChangeForm, self).__init__(*args, **kwargs)
del self.fields['username']
Empty file.
Empty file.
102 changes: 102 additions & 0 deletions emailusernames/management/commands/createsuperuser.py
@@ -0,0 +1,102 @@
"""
Management utility to create superusers.
Replace default behaviour to use emails as usernames.
"""

import getpass
import re
import sys
from optparse import make_option
from django.contrib.auth.models import User
from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError
from django.utils.translation import ugettext as _
from email_as_username.utils import create_superuser


RE_VALID_USERNAME = re.compile('[\w.@+-]+$')

EMAIL_RE = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain


def is_valid_email(value):
if not EMAIL_RE.search(value):
raise exceptions.ValidationError(_('Enter a valid e-mail address.'))


class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--email', dest='email', default=None,
help='Specifies the email address for the superuser.'),
make_option('--noinput', action='store_false', dest='interactive', default=True,
help=('Tells Django to NOT prompt the user for input of any kind. '
'You must use --username and --email with --noinput, and '
'superusers created with --noinput will not be able to log '
'in until they\'re given a valid password.')),
)
help = 'Used to create a superuser.'

def handle(self, *args, **options):
email = options.get('email', None)
interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))

# Do quick and dirty validation if --noinput
if not interactive:
if not email:
raise CommandError("You must use --email with --noinput.")
try:
is_valid_email(email)
except exceptions.ValidationError:
raise CommandError("Invalid email address.")

# If not provided, create the user with an unusable password
password = None

# Prompt for username/email/password. Enclose this whole thing in a
# try/except to trap for a keyboard interrupt and exit gracefully.
if interactive:
try:
# Get an email
while 1:
if not email:
email = raw_input('E-mail address: ')

try:
is_valid_email(email)
except exceptions.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n")
email = None

try:
User.objects.get(email__iexact=email)
except User.DoesNotExist:
break
else:
sys.stderr.write("Error: That email is already taken.\n")
email = None

# Get a password
while 1:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n")
password = None
continue
if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
password = None
continue
break
except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1)

create_superuser(email, password)
if verbosity >= 1:
self.stdout.write("Superuser created successfully.\n")

0 comments on commit 735249c

Please sign in to comment.