forked from relekang/django-nopassword
-
Notifications
You must be signed in to change notification settings - Fork 1
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
1 parent
81a184f
commit eb33fb8
Showing
27 changed files
with
446 additions
and
366 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
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 | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,5 @@ | |
|
||
try: | ||
from .sms import TwilioBackend # noqa | ||
except ImportError: | ||
except ImportError: # pragma: no cover | ||
pass |
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
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 | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,27 @@ | ||
# -*- coding: utf-8 -*- | ||
from django.conf import settings | ||
from django.core.mail import EmailMultiAlternatives | ||
from django.template.loader import render_to_string | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from .base import NoPasswordBackend | ||
from nopassword.backends.base import NoPasswordBackend | ||
|
||
|
||
class EmailBackend(NoPasswordBackend): | ||
template_name = 'registration/login_code_request_email.html' | ||
html_template_name = None | ||
subject_template_name = 'registration/login_code_request_subject.txt' | ||
from_email = None | ||
|
||
def send_login_code(self, code, secure=False, host=None, **kwargs): | ||
subject = getattr(settings, 'NOPASSWORD_LOGIN_EMAIL_SUBJECT', _('Login code')) | ||
to_email = [code.user.email] | ||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', '[email protected]') | ||
def send_login_code(self, code, context, **kwargs): | ||
to_email = code.user.email | ||
subject = render_to_string(self.subject_template_name, context) | ||
# Email subject *must not* contain newlines | ||
subject = ''.join(subject.splitlines()) | ||
body = render_to_string(self.template_name, context) | ||
|
||
context = {'url': code.login_url(secure=secure, host=host), 'code': code} | ||
text_content = render_to_string('registration/login_email.txt', context) | ||
html_content = render_to_string('registration/login_email.html', context) | ||
email_message = EmailMultiAlternatives(subject, body, self.from_email, [to_email]) | ||
|
||
msg = EmailMultiAlternatives(subject, text_content, from_email, to_email) | ||
msg.attach_alternative(html_content, 'text/html') | ||
msg.send() | ||
if self.html_template_name is not None: | ||
html_email = render_to_string(self.html_template_name, context) | ||
email_message.attach_alternative(html_email, 'text/html') | ||
|
||
email_message.send() |
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
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 | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,133 @@ | ||
# -*- coding: utf-8 -*- | ||
from django import forms | ||
from django.contrib.auth import get_user_model | ||
from django.utils.text import capfirst | ||
from django.conf import settings | ||
from django.contrib.auth import authenticate, get_backends, get_user_model | ||
from django.contrib.sites.shortcuts import get_current_site | ||
from django.core.exceptions import ImproperlyConfigured | ||
from django.shortcuts import resolve_url | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from nopassword.models import LoginCode | ||
from nopassword import models | ||
|
||
|
||
class AuthenticationForm(forms.Form): | ||
""" | ||
Base class for authenticating users. Extend this to get a form that accepts | ||
username logins. | ||
""" | ||
username = forms.CharField() | ||
|
||
class LoginCodeRequestForm(forms.Form): | ||
error_messages = { | ||
'invalid_login': _("Please enter a correct username. " | ||
"Note that it is case-sensitive."), | ||
'no_cookies': _("Your Web browser doesn't appear to have cookies " | ||
"enabled. Cookies are required for logging in."), | ||
'invalid_username': _( | ||
"Please enter a correct %(username)s. " | ||
"Note that it is case-sensitive." | ||
), | ||
'inactive': _("This account is inactive."), | ||
} | ||
|
||
def __init__(self, request=None, *args, **kwargs): | ||
""" | ||
If request is passed in, the form will validate that cookies are | ||
enabled. Note that the request (a HttpRequest object) must have set a | ||
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before | ||
running this validation. | ||
""" | ||
super(AuthenticationForm, self).__init__(*args, **kwargs) | ||
def __init__(self, *args, **kwargs): | ||
super(LoginCodeRequestForm, self).__init__(*args, **kwargs) | ||
|
||
self.request = request | ||
self.login_code = None | ||
self.username_field = get_user_model()._meta.get_field(get_user_model().USERNAME_FIELD) | ||
self.fields['username'].max_length = self.username_field.max_length or 254 | ||
|
||
if self.fields['username'].label is None: | ||
self.fields['username'].label = capfirst(self.username_field.verbose_name) | ||
self.fields['username'] = self.username_field.formfield() | ||
|
||
def clean_username(self): | ||
username = self.cleaned_data['username'] | ||
|
||
try: | ||
user = get_user_model()._default_manager.get_by_natural_key(username) | ||
except get_user_model().DoesNotExist: | ||
raise forms.ValidationError(self.error_messages['invalid_login']) | ||
raise forms.ValidationError( | ||
self.error_messages['invalid_username'], | ||
code='invalid_username', | ||
params={'username': self.username_field.verbose_name}, | ||
) | ||
|
||
if not user.is_active: | ||
raise forms.ValidationError(self.error_messages['invalid_login']) | ||
raise forms.ValidationError( | ||
self.error_messages['inactive'], | ||
code='inactive', | ||
) | ||
|
||
self.login_code = LoginCode.create_code_for_user(user) | ||
self.cleaned_data['login_code'] = models.LoginCode.create_code_for_user(user) | ||
|
||
return username | ||
|
||
def clean(self): | ||
self.check_for_test_cookie() | ||
def save(self, request, login_url=None, domain_override=None, extra_context=None): | ||
login_code = self.cleaned_data['login_code'] | ||
login_code.next = request.GET.get('next') | ||
login_code.save() | ||
|
||
if not domain_override: | ||
current_site = get_current_site(request) | ||
site_name = current_site.name | ||
domain = current_site.domain | ||
else: | ||
site_name = domain = domain_override | ||
|
||
url = '{}://{}{}?code={}&next={}'.format( | ||
'https' if request.is_secure() else 'http', | ||
domain, | ||
resolve_url(login_url or settings.LOGIN_URL), | ||
login_code.code, | ||
login_code.next, | ||
) | ||
|
||
context = { | ||
'domain': domain, | ||
'site_name': site_name, | ||
'code': login_code.code, | ||
'url': url, | ||
} | ||
|
||
if extra_context: | ||
context.update(extra_context) | ||
|
||
self.send_login_code(login_code, context) | ||
|
||
def send_login_code(self, login_code, context, **kwargs): | ||
for backend in get_backends(): | ||
if hasattr(backend, 'send_login_code'): | ||
backend.send_login_code(login_code, context, **kwargs) | ||
break | ||
else: | ||
raise ImproperlyConfigured( | ||
'Please add a nopassword authentication backend to settings, ' | ||
'e.g. `nopassword.backends.EmailBackend`' | ||
) | ||
|
||
|
||
class LoginForm(forms.Form): | ||
code = forms.ModelChoiceField( | ||
label=_('Login code'), | ||
queryset=models.LoginCode.objects.select_related('user'), | ||
to_field_name='code', | ||
widget=forms.TextInput, | ||
error_messages={ | ||
'invalid_choice': _('Login code is invalid. It might have expired.'), | ||
}, | ||
) | ||
|
||
error_messages = { | ||
'invalid_code': _("Unable to log in with provided login code."), | ||
} | ||
|
||
def __init__(self, request=None, *args, **kwargs): | ||
super(LoginForm, self).__init__(*args, **kwargs) | ||
|
||
self.request = request | ||
|
||
def clean_code(self): | ||
code = self.cleaned_data['code'] | ||
username = code.user.get_username() | ||
user = authenticate(self.request, **{ | ||
get_user_model().USERNAME_FIELD: username, | ||
'code': code.code, | ||
}) | ||
|
||
if not user: | ||
raise forms.ValidationError( | ||
self.error_messages['invalid_code'], | ||
code='invalid_code', | ||
) | ||
|
||
self.cleaned_data['user'] = user | ||
|
||
def check_for_test_cookie(self): | ||
if self.request and not self.request.session.test_cookie_worked(): | ||
raise forms.ValidationError(self.error_messages['no_cookies']) | ||
return code | ||
|
||
def save(self, request): | ||
self.login_code.next = request.GET.get('next') | ||
self.login_code.save() | ||
self.login_code.send_login_code(secure=request.is_secure(), host=request.get_host()) | ||
def get_user(self): | ||
return self.cleaned_data.get('user') |
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
Oops, something went wrong.