diff --git a/seahub/auth/forms.py b/seahub/auth/forms.py index 87adcac9bf..00fd63b0b5 100644 --- a/seahub/auth/forms.py +++ b/seahub/auth/forms.py @@ -28,10 +28,7 @@ class AuthenticationForm(forms.Form): """ self.request = request self.user_cache = None - use_captcha = kwargs.pop('use_captcha', False) super(AuthenticationForm, self).__init__(*args, **kwargs) - if use_captcha: - self.fields['captcha'] = CaptchaField() def clean(self): username = self.cleaned_data.get('username') @@ -59,6 +56,9 @@ class AuthenticationForm(forms.Form): def get_user(self): return self.user_cache +class CaptchaAuthenticationForm(AuthenticationForm): + captcha = CaptchaField() + class PasswordResetForm(forms.Form): email = forms.EmailField(label=_("E-mail"), max_length=255) diff --git a/seahub/auth/views.py b/seahub/auth/views.py index dcde4ac5a5..5f2fc78a1f 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -3,6 +3,7 @@ import logging from django.conf import settings # Avoid shadowing the login() view below. from django.views.decorators.csrf import csrf_protect +from django.core.cache import cache from django.core.urlresolvers import reverse from django.contrib import messages 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 login as auth_login 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.tokens import default_token_generator from seahub.base.accounts import User 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 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 @never_cache 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)) 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": - form = authentication_form(data=request.POST, use_captcha=use_captcha) - if form.is_valid(): - # 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, form.get_user()) - - if request.session.test_cookie_worked(): - request.session.delete_test_cookie() - - login_attempt.delete() - return HttpResponseRedirect(redirect_to) + if request.REQUEST.get('captcha_0', '') != '': + # have captcha + form = CaptchaAuthenticationForm(data=request.POST) + if form.is_valid(): + # captcha & passwod is valid, log user in + return log_user_in(request, form.get_user(), redirect_to) + # else: + # show page with captcha else: - login_attempt.failed_attempts += 1 - login_attempt.save() - if login_attempt.failed_attempts == LOGIN_ATTEMPT_LIMIT: - form = authentication_form(data=request.POST, use_captcha=True) - + form = authentication_form(data=request.POST) + if form.is_valid(): + # password is valid, log user in + 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: + ### GET form = authentication_form(request) request.session.set_test_cookie() @@ -90,11 +100,11 @@ def login(request, template_name='registration/login.html', current_site = RequestSite(request) return render_to_response(template_name, { - 'form': form, - redirect_field_name: redirect_to, - 'site': current_site, - 'site_name': current_site.name, - }, context_instance=RequestContext(request)) + 'form': form, + redirect_field_name: redirect_to, + 'site': current_site, + 'site_name': current_site.name, + }, context_instance=RequestContext(request)) 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." diff --git a/seahub/base/accounts.py b/seahub/base/accounts.py index c08bf805a9..f7d6dbc5a2 100644 --- a/seahub/base/accounts.py +++ b/seahub/base/accounts.py @@ -218,7 +218,8 @@ class AuthBackend(object): return user except User.DoesNotExist: return None - + +########## Register related class RegistrationBackend(object): """ A registration backend which follows a simple workflow: diff --git a/seahub/base/models.py b/seahub/base/models.py index ddae04c4c7..31e55337d8 100644 --- a/seahub/base/models.py +++ b/seahub/base/models.py @@ -1,6 +1,7 @@ import datetime import logging import re +from django.conf import settings from django.db import models from django.db.models.signals import post_save 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.shortcuts import get_first_object_or_none +from seahub.base.fields import LowerCaseCharField from seahub.base.templatetags.seahub_tags import at_pattern from seahub.group.models import GroupMessage 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.save() user_logged_in.connect(update_last_login) - + ###### Deprecated class InnerPubMsg(models.Model): """ @@ -323,10 +325,4 @@ class InnerPubMsgReply(models.Model): # detail=msg_id) # 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'] + diff --git a/seahub/settings.py b/seahub/settings.py index 1c464c5ae6..f3d00147df 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -346,9 +346,8 @@ LOGGING = { } #Login Attempt -import datetime LOGIN_ATTEMPT_LIMIT = 3 -LOGIN_ATTEMPT_TIMEOUT = datetime.timedelta(minutes=15) +LOGIN_ATTEMPT_TIMEOUT = 15 * 60 # in seconds (default: 15 minutes) ################# # Email sending # diff --git a/seahub/templates/registration/login.html b/seahub/templates/registration/login.html index 30d0607c04..55fe49d83b 100644 --- a/seahub/templates/registration/login.html +++ b/seahub/templates/registration/login.html @@ -13,7 +13,7 @@ {% if form.errors %} {% if form.captcha.errors %} -
{% trans "Incorrect CAPTCHA" %}
+{{ form.captcha.errors}}
{% else %}{% trans "Incorrect email or password" %}
{% endif %}