diff --git a/docs/releases/2.5.rst b/docs/releases/2.5.rst index 31e8fb9197..e348405a14 100644 --- a/docs/releases/2.5.rst +++ b/docs/releases/2.5.rst @@ -62,6 +62,9 @@ Other Changes in the tools menu. Finally a new tab in the document view has been added called "Duplicates" that will list all duplicates of the currently selected document. +- Add "Remember me" checkbox in the login form. +- Add AUTHENTICATION_MAXIMUM_SESSION_LENGTH configuration setting for the maximum + time an user's login session will remain valid. Defaults to 30 days. Removals -------- diff --git a/mayan/apps/authentication/forms.py b/mayan/apps/authentication/forms.py index 68c6db3a2f..e39f7471dc 100644 --- a/mayan/apps/authentication/forms.py +++ b/mayan/apps/authentication/forms.py @@ -4,6 +4,7 @@ import warnings from django import forms from django.contrib.auth import authenticate +from django.contrib.auth.forms import AuthenticationForm from django.utils.translation import ugettext_lazy as _ from common.widgets import EmailInput @@ -19,6 +20,7 @@ class EmailAuthenticationForm(forms.Form): password = forms.CharField( label=_('Password'), widget=forms.PasswordInput ) + remember_me = forms.BooleanField(label=_('Remember me'), required=False) error_messages = { 'invalid_login': _('Please enter a correct email and password. ' @@ -64,3 +66,7 @@ class EmailAuthenticationForm(forms.Form): def get_user(self): return self.user_cache + + +class UsernameAuthenticationForm(AuthenticationForm): + remember_me = forms.BooleanField(label=_('Remember me'), required=False) diff --git a/mayan/apps/authentication/literals.py b/mayan/apps/authentication/literals.py new file mode 100644 index 0000000000..086ebb7c20 --- /dev/null +++ b/mayan/apps/authentication/literals.py @@ -0,0 +1,4 @@ +from __future__ import unicode_literals + +DEFAULT_LOGIN_METHOD = 'username' +DEFAULT_MAXIMUM_SESSION_LENGTH = 60 * 60 * 24 * 30 # 30 days diff --git a/mayan/apps/authentication/settings.py b/mayan/apps/authentication/settings.py index 56d7ee2410..991f163f8a 100644 --- a/mayan/apps/authentication/settings.py +++ b/mayan/apps/authentication/settings.py @@ -4,11 +4,20 @@ from django.utils.translation import ugettext_lazy as _ from smart_settings import Namespace +from .literals import DEFAULT_LOGIN_METHOD, DEFAULT_MAXIMUM_SESSION_LENGTH + namespace = Namespace(name='authentication', label=_('Authentication')) setting_login_method = namespace.add_setting( - global_name='AUTHENTICATION_LOGIN_METHOD', default='username', + global_name='AUTHENTICATION_LOGIN_METHOD', default=DEFAULT_LOGIN_METHOD, help_text=_( 'Controls the mechanism used to authenticated user. Options are: ' 'username, email' ) ) +setting_maximum_session_length = namespace.add_setting( + global_name='AUTHENTICATION_MAXIMUM_SESSION_LENGTH', + default=DEFAULT_MAXIMUM_SESSION_LENGTH, help_text=_( + 'Maximum type an user clicking the "Remember me" checkbox will ' + 'remain logged in. Value is time in seconds.' + ) +) diff --git a/mayan/apps/authentication/tests/test_views.py b/mayan/apps/authentication/tests/test_views.py index 56282702f1..c24c1fe66d 100644 --- a/mayan/apps/authentication/tests/test_views.py +++ b/mayan/apps/authentication/tests/test_views.py @@ -11,6 +11,8 @@ from user_management.tests.literals import ( TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME ) +from ..settings import setting_maximum_session_length + from .literals import TEST_EMAIL_AUTHENTICATION_BACKEND @@ -100,3 +102,73 @@ class UserLoginTestCase(BaseTestCase): response = self.client.get(reverse('documents:document_list')) # We didn't get redirected to the login URL self.assertEqual(response.status_code, 200) + + @override_settings(AUTHENTICATION_LOGIN_METHOD='username') + def test_username_remember_me(self): + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'username': TEST_ADMIN_USERNAME, + 'password': TEST_ADMIN_PASSWORD, + 'remember_me': True + }, follow=True + ) + + response = self.client.get(reverse('documents:document_list')) + self.assertEqual(response.status_code, 200) + + self.assertEqual( + self.client.session.get_expiry_age(), + setting_maximum_session_length.value + ) + self.assertFalse(self.client.session.get_expire_at_browser_close()) + + @override_settings(AUTHENTICATION_LOGIN_METHOD='username') + def test_username_dont_remember_me(self): + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'username': TEST_ADMIN_USERNAME, + 'password': TEST_ADMIN_PASSWORD, + 'remember_me': False + }, follow=True + ) + + response = self.client.get(reverse('documents:document_list')) + self.assertEqual(response.status_code, 200) + + self.assertTrue(self.client.session.get_expire_at_browser_close()) + + @override_settings(AUTHENTICATION_LOGIN_METHOD='email') + def test_email_remember_me(self): + with self.settings(AUTHENTICATION_BACKENDS=(TEST_EMAIL_AUTHENTICATION_BACKEND,)): + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'email': TEST_ADMIN_EMAIL, + 'password': TEST_ADMIN_PASSWORD, + 'remember_me': True + }, follow=True + ) + + response = self.client.get(reverse('documents:document_list')) + self.assertEqual(response.status_code, 200) + + self.assertEqual( + self.client.session.get_expiry_age(), + setting_maximum_session_length.value + ) + self.assertFalse(self.client.session.get_expire_at_browser_close()) + + @override_settings(AUTHENTICATION_LOGIN_METHOD='email') + def test_email_dont_remember_me(self): + with self.settings(AUTHENTICATION_BACKENDS=(TEST_EMAIL_AUTHENTICATION_BACKEND,)): + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'email': TEST_ADMIN_EMAIL, + 'password': TEST_ADMIN_PASSWORD, + 'remember_me': False + }, follow=True + ) + + response = self.client.get(reverse('documents:document_list')) + self.assertEqual(response.status_code, 200) + + self.assertTrue(self.client.session.get_expire_at_browser_close()) diff --git a/mayan/apps/authentication/views.py b/mayan/apps/authentication/views.py index 8d21556f7c..81c974cd5a 100644 --- a/mayan/apps/authentication/views.py +++ b/mayan/apps/authentication/views.py @@ -10,8 +10,8 @@ from django.utils.translation import ugettext_lazy as _ from stronghold.decorators import public -from .forms import EmailAuthenticationForm -from .settings import setting_login_method +from .forms import EmailAuthenticationForm, UsernameAuthenticationForm +from .settings import setting_login_method, setting_maximum_session_length @public @@ -24,10 +24,23 @@ def login_view(request): if setting_login_method.value == 'email': kwargs['authentication_form'] = EmailAuthenticationForm + else: + kwargs['authentication_form'] = UsernameAuthenticationForm if not request.user.is_authenticated(): context = {'appearance_type': 'plain'} - return login(request, extra_context=context, **kwargs) + + result = login(request, extra_context=context, **kwargs) + if request.method == 'POST': + form = kwargs['authentication_form'](request, data=request.POST) + if form.is_valid(): + if form.cleaned_data['remember_me']: + request.session.set_expiry( + setting_maximum_session_length.value + ) + else: + request.session.set_expiry(0) + return result else: return HttpResponseRedirect(reverse(settings.LOGIN_REDIRECT_URL))