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:
@@ -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)
|
||||||
|
|
||||||
|
@@ -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."
|
||||||
|
@@ -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:
|
||||||
|
@@ -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']
|
|
||||||
|
@@ -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 #
|
||||||
|
@@ -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 %}
|
||||||
|
Reference in New Issue
Block a user