1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-13 13:50:07 +00:00

Use cache to record user login attempts

This commit is contained in:
zhengxie
2013-11-09 10:33:53 +08:00
parent bdbe73f6bc
commit 017325290c
6 changed files with 66 additions and 60 deletions

View File

@@ -28,10 +28,7 @@ class AuthenticationForm(forms.Form):
""" """
self.request = request self.request = request
self.user_cache = None self.user_cache = None
use_captcha = kwargs.pop('use_captcha', False)
super(AuthenticationForm, self).__init__(*args, **kwargs) super(AuthenticationForm, self).__init__(*args, **kwargs)
if use_captcha:
self.fields['captcha'] = CaptchaField()
def clean(self): def clean(self):
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
@@ -59,6 +56,9 @@ class AuthenticationForm(forms.Form):
def get_user(self): def get_user(self):
return self.user_cache return self.user_cache
class CaptchaAuthenticationForm(AuthenticationForm):
captcha = CaptchaField()
class PasswordResetForm(forms.Form): class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("E-mail"), max_length=255) email = forms.EmailField(label=_("E-mail"), max_length=255)

View File

@@ -3,6 +3,7 @@ import logging
from django.conf import settings from django.conf import settings
# Avoid shadowing the login() view below. # Avoid shadowing the login() view below.
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
from django.shortcuts import render_to_response, get_object_or_404 from django.shortcuts import render_to_response, get_object_or_404
@@ -16,18 +17,40 @@ from django.views.decorators.cache import never_cache
from seahub.auth import REDIRECT_FIELD_NAME from seahub.auth import REDIRECT_FIELD_NAME
from seahub.auth import login as auth_login from seahub.auth import login as auth_login
from seahub.auth.decorators import login_required from seahub.auth.decorators import login_required
from seahub.auth.forms import AuthenticationForm from seahub.auth.forms import AuthenticationForm, CaptchaAuthenticationForm
from seahub.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm from seahub.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm
from seahub.auth.tokens import default_token_generator from seahub.auth.tokens import default_token_generator
from seahub.base.accounts import User from seahub.base.accounts import User
from seahub.base.models import UserLoginAttempt from seahub.base.models import UserLoginAttempt
from settings import LOGIN_ATTEMPT_LIMIT, LOGIN_ATTEMPT_TIMEOUT
from datetime import datetime
# Get an instance of a logger # Get an instance of a logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
LOGIN_ATTEMPT_PREFIX = 'UserLoginAttempt_'
def log_user_in(request, user, redirect_to):
# Light security check -- make sure redirect_to isn't garbage.
if not redirect_to or ' ' in redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
# Heavier security check -- redirects to http://example.com should
# not be allowed, but things like /view/?param=http://example.com
# should be allowed. This regex checks if there is a '//' *before* a
# question mark.
elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
redirect_to = settings.LOGIN_REDIRECT_URL
# Okay, security checks complete. Log the user in.
auth_login(request, user)
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
cache.delete(LOGIN_ATTEMPT_PREFIX+user.username)
return HttpResponseRedirect(redirect_to)
@csrf_protect @csrf_protect
@never_cache @never_cache
def login(request, template_name='registration/login.html', def login(request, template_name='registration/login.html',
@@ -40,46 +63,33 @@ def login(request, template_name='registration/login.html',
return HttpResponseRedirect(reverse(redirect_if_logged_in)) return HttpResponseRedirect(reverse(redirect_if_logged_in))
redirect_to = request.REQUEST.get(redirect_field_name, '') redirect_to = request.REQUEST.get(redirect_field_name, '')
username = request.REQUEST.get('username', '')
if username:
login_attempt, created = UserLoginAttempt.objects.get_or_create(username=username)
if login_attempt.last_attempt_time + LOGIN_ATTEMPT_TIMEOUT < datetime.now():
login_attempt.delete()
login_attempt = UserLoginAttempt.objects.create(username=username)
if login_attempt.failed_attempts >= LOGIN_ATTEMPT_LIMIT:
use_captcha = True
else:
use_captcha = False
if request.method == "POST": if request.method == "POST":
form = authentication_form(data=request.POST, use_captcha=use_captcha) if request.REQUEST.get('captcha_0', '') != '':
if form.is_valid(): # have captcha
# Light security check -- make sure redirect_to isn't garbage. form = CaptchaAuthenticationForm(data=request.POST)
if not redirect_to or ' ' in redirect_to: if form.is_valid():
redirect_to = settings.LOGIN_REDIRECT_URL # captcha & passwod is valid, log user in
return log_user_in(request, form.get_user(), redirect_to)
# Heavier security check -- redirects to http://example.com should # else:
# not be allowed, but things like /view/?param=http://example.com # show page with captcha
# should be allowed. This regex checks if there is a '//' *before* a
# question mark.
elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
redirect_to = settings.LOGIN_REDIRECT_URL
# Okay, security checks complete. Log the user in.
auth_login(request, form.get_user())
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
login_attempt.delete()
return HttpResponseRedirect(redirect_to)
else: else:
login_attempt.failed_attempts += 1 form = authentication_form(data=request.POST)
login_attempt.save() if form.is_valid():
if login_attempt.failed_attempts == LOGIN_ATTEMPT_LIMIT: # password is valid, log user in
form = authentication_form(data=request.POST, use_captcha=True) return log_user_in(request, form.get_user(), redirect_to)
else:
username = request.REQUEST.get('username', '')
failed_attempt = cache.get(LOGIN_ATTEMPT_PREFIX+username, 0)
if failed_attempt >= settings.LOGIN_ATTEMPT_LIMIT:
form = CaptchaAuthenticationForm()
else:
failed_attempt += 1
cache.set(LOGIN_ATTEMPT_PREFIX+username, failed_attempt,
settings.LOGIN_ATTEMPT_TIMEOUT)
form = authentication_form(data=request.POST)
else: else:
### GET
form = authentication_form(request) form = authentication_form(request)
request.session.set_test_cookie() request.session.set_test_cookie()
@@ -90,11 +100,11 @@ def login(request, template_name='registration/login.html',
current_site = RequestSite(request) current_site = RequestSite(request)
return render_to_response(template_name, { return render_to_response(template_name, {
'form': form, 'form': form,
redirect_field_name: redirect_to, redirect_field_name: redirect_to,
'site': current_site, 'site': current_site,
'site_name': current_site.name, 'site_name': current_site.name,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME): def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME):
"Logs out the user and displays 'You are logged out' message." "Logs out the user and displays 'You are logged out' message."

View File

@@ -218,7 +218,8 @@ class AuthBackend(object):
return user return user
except User.DoesNotExist: except User.DoesNotExist:
return None return None
########## Register related
class RegistrationBackend(object): class RegistrationBackend(object):
""" """
A registration backend which follows a simple workflow: A registration backend which follows a simple workflow:

View File

@@ -1,6 +1,7 @@
import datetime import datetime
import logging import logging
import re import re
from django.conf import settings
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
@@ -11,6 +12,7 @@ from seaserv import seafile_api, get_emailusers
from seahub.auth.signals import user_logged_in from seahub.auth.signals import user_logged_in
from seahub.shortcuts import get_first_object_or_none from seahub.shortcuts import get_first_object_or_none
from seahub.base.fields import LowerCaseCharField
from seahub.base.templatetags.seahub_tags import at_pattern from seahub.base.templatetags.seahub_tags import at_pattern
from seahub.group.models import GroupMessage from seahub.group.models import GroupMessage
from seahub.notifications.models import UserNotification from seahub.notifications.models import UserNotification
@@ -221,7 +223,7 @@ def update_last_login(sender, user, **kwargs):
user_last_login.last_login = timezone.now() user_last_login.last_login = timezone.now()
user_last_login.save() user_last_login.save()
user_logged_in.connect(update_last_login) user_logged_in.connect(update_last_login)
###### Deprecated ###### Deprecated
class InnerPubMsg(models.Model): class InnerPubMsg(models.Model):
""" """
@@ -323,10 +325,4 @@ class InnerPubMsgReply(models.Model):
# detail=msg_id) # detail=msg_id)
# n.save() # n.save()
class UserLoginAttempt(models.Model):
username = models.CharField(max_length=255)
last_attempt_time = models.DateTimeField(auto_now_add=True)
failed_attempts = models.IntegerField(default=0)
class Meta:
ordering = ['-last_attempt_time']

View File

@@ -346,9 +346,8 @@ LOGGING = {
} }
#Login Attempt #Login Attempt
import datetime
LOGIN_ATTEMPT_LIMIT = 3 LOGIN_ATTEMPT_LIMIT = 3
LOGIN_ATTEMPT_TIMEOUT = datetime.timedelta(minutes=15) LOGIN_ATTEMPT_TIMEOUT = 15 * 60 # in seconds (default: 15 minutes)
################# #################
# Email sending # # Email sending #

View File

@@ -13,7 +13,7 @@
<input type="hidden" name="next" value="{% if next %}{{ next|escape }}{% else %}{{ SITE_ROOT }}{% endif %}" /> <input type="hidden" name="next" value="{% if next %}{{ next|escape }}{% else %}{{ SITE_ROOT }}{% endif %}" />
{% if form.errors %} {% if form.errors %}
{% if form.captcha.errors %} {% if form.captcha.errors %}
<p class="error">{% trans "Incorrect CAPTCHA" %}</p> <p class="error">{{ form.captcha.errors}}</p>
{% else %} {% else %}
<p class="error">{% trans "Incorrect email or password" %}</p> <p class="error">{% trans "Incorrect email or password" %}</p>
{% endif %} {% endif %}