Skip to content

Commit

Permalink
Merge 84edb3b into c8596ea
Browse files Browse the repository at this point in the history
  • Loading branch information
dodobas committed Nov 27, 2018
2 parents c8596ea + 84edb3b commit 8aa607b
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 44 deletions.
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)

0 comments on commit 8aa607b

Please sign in to comment.