This repository has been archived by the owner on Dec 6, 2021. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 735249c
Showing
14 changed files
with
618 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,3 @@ | |||
*.pyc | |||
MANIFEST | |||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1 @@ | |||
__version__ = '0.1.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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") |
Oops, something went wrong.