Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Django 2.1.x #126

Merged
merged 1 commit into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
language: python
sudo: false

addons:
postgresql: "9.6"

python:
- 3.4
- 3.5
Expand All @@ -9,6 +12,12 @@ python:
env:
- DJANGO_VERSION=1.11.16
- DJANGO_VERSION=2.0.9
- DJANGO_VERSION=2.1.3

matrix:
exclude:
- python: 3.4
env: DJANGO_VERSION=2.1.3

install:
- pip install -q -r requirements/base.txt
Expand Down
2 changes: 1 addition & 1 deletion smartmin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import unicode_literals

__version__ = '2.0.2'
__version__ = '2.1.0'
2 changes: 1 addition & 1 deletion smartmin/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class CaseInsensitiveBackend(ModelBackend):
Authenticates against settings.AUTH_USER_MODEL.
"""

def authenticate(self, username=None, password=None, **kwargs):
def authenticate(self, request, username=None, password=None, **kwargs):
User = get_user_model()
try:
user = User.objects.get(username__iexact=username)
Expand Down
14 changes: 10 additions & 4 deletions smartmin/users/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from django.conf import settings
from django.conf.urls import url
from django.contrib.auth.views import logout
from .views import login, UserCRUDL
from django.contrib.auth.views import LogoutView
from .views import Login, UserCRUDL

logout_url = getattr(settings, 'LOGOUT_REDIRECT_URL', None)

urlpatterns = [
url(r'^login/$', login, dict(template_name='smartmin/users/login.html'), name="users.user_login"),
url(r'^logout/$', logout, dict(redirect_field_name='go', next_page=logout_url), name="users.user_logout"),
url(
r'^login/$', Login.as_view(), dict(template_name='smartmin/users/login.html'),
name="users.user_login"
),
url(
r'^logout/$', LogoutView.as_view(), dict(redirect_field_name='go', next_page=logout_url),
name="users.user_logout"
),
]

urlpatterns += UserCRUDL().as_urlpatterns()
81 changes: 43 additions & 38 deletions smartmin/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from django import forms
from django.conf import settings
from django.contrib import messages, auth
from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.auth.views import login as django_login
from django.contrib.auth.views import LoginView
from django.core.mail import send_mail
from django.urls import reverse
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -358,7 +357,8 @@ def derive_success_message(self):

def pre_process(self, request, *args, **kwargs):
user = self.get_object()
login(request)

Login.as_view()(request)

# After logging in it is important to change the user stored in the session
# otherwise the user will remain the same
Expand Down Expand Up @@ -425,47 +425,52 @@ def get_context_data(self, *args, **kwargs):
return context


def login(request, template_name='smartmin/users/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
current_app=None, extra_context=None):
class Login(LoginView):
template_name = 'smartmin/users/login.html'

lockout_timeout = getattr(settings, 'USER_LOCKOUT_TIMEOUT', 10)
failed_login_limit = getattr(settings, 'USER_FAILED_LOGIN_LIMIT', 5)
allow_email_recovery = getattr(settings, 'USER_ALLOW_EMAIL_RECOVERY', True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

if request.method == "POST":
if 'username' in request.POST and 'password' in request.POST:
# we are using AuthenticationForm in which username is CharField with strip=True that automatically strips
# whitespace characters, we need to copy that behaviour
username = request.POST['username'].strip()
context['allow_email_recovery'] = getattr(settings, 'USER_ALLOW_EMAIL_RECOVERY', True)

user = get_user_model().objects.filter(username__iexact=username).first()
return context

# this could be a valid login by a user
if user:
def post(self, request, *args, **kwargs):
form = self.get_form()

# clean form data
form_is_valid = form.is_valid()

lockout_timeout = getattr(settings, 'USER_LOCKOUT_TIMEOUT', 10)
failed_login_limit = getattr(settings, 'USER_FAILED_LOGIN_LIMIT', 5)

# incorrect password? create a failed login token
valid_password = user.check_password(request.POST['password'])
if not valid_password:
FailedLogin.objects.create(user=user)
user = get_user_model().objects.filter(username__iexact=form.cleaned_data.get('username')).first()

bad_interval = timezone.now() - timedelta(minutes=lockout_timeout)
failures = FailedLogin.objects.filter(user=user)
# this could be a valid login by a user
if user:

# if the failures reset after a period of time, then limit our query to that interval
if lockout_timeout > 0:
failures = failures.filter(failed_on__gt=bad_interval)
# incorrect password? create a failed login token
valid_password = user.check_password(form.cleaned_data.get('password'))
if not valid_password:
FailedLogin.objects.create(user=user)

# if there are too many failed logins, take them to the failed page
if len(failures) >= failed_login_limit:
return HttpResponseRedirect(reverse('users.user_failed'))
bad_interval = timezone.now() - timedelta(minutes=lockout_timeout)
failures = FailedLogin.objects.filter(user=user)

# delete failed logins if the password is valid
elif valid_password:
FailedLogin.objects.filter(user=user).delete()
# if the failures reset after a period of time, then limit our query to that interval
if lockout_timeout > 0:
failures = failures.filter(failed_on__gt=bad_interval)

# if there are too many failed logins, take them to the failed page
if len(failures) >= failed_login_limit:
return HttpResponseRedirect(reverse('users.user_failed'))

# delete failed logins if the password is valid
elif valid_password:
FailedLogin.objects.filter(user=user).delete()

return django_login(request, template_name='smartmin/users/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
extra_context=dict(allow_email_recovery=allow_email_recovery))
# pass through the normal login process
if form_is_valid:
return self.form_valid(form)
else:
return self.form_invalid(form)