From a48fe86791834ac874c4b1c02ad1be671905a69a Mon Sep 17 00:00:00 2001 From: Finn Stutzenstein Date: Thu, 18 Mar 2021 16:06:55 +0100 Subject: [PATCH] Add login errors for inactive users --- server/openslides/global_settings.py | 16 +++----- server/openslides/users/views.py | 20 ++++++---- server/openslides/utils/auth_backend.py | 14 +++++++ server/tests/integration/users/test_views.py | 41 ++++++++++++++++++++ 4 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 server/openslides/utils/auth_backend.py diff --git a/server/openslides/global_settings.py b/server/openslides/global_settings.py index adb45929c..4de1b260d 100644 --- a/server/openslides/global_settings.py +++ b/server/openslides/global_settings.py @@ -99,18 +99,21 @@ STATICFILES_DIRS = [os.path.join(MODULE_DIR, "static")] + [ STATIC_ROOT = os.path.join(OPENSLIDES_USER_DATA_DIR, "collected-static") # Files -# https://docs.djangoproject.com/en/1.10/topics/files/ +# https://docs.djangoproject.com/en/2.2/topics/files/ MEDIA_ROOT = os.path.join(OPENSLIDES_USER_DATA_DIR, "media", "") +MEDIA_URL = "/media/" # Sessions and user authentication -# https://docs.djangoproject.com/en/1.10/topics/http/sessions/ -# https://docs.djangoproject.com/en/1.10/topics/auth/ +# https://docs.djangoproject.com/en/2.2/topics/http/sessions/ +# https://docs.djangoproject.com/en/2.2/topics/auth/ AUTH_USER_MODEL = "users.User" AUTH_GROUP_MODEL = "users.Group" +AUTHENTICATION_BACKENDS = ["openslides.utils.auth_backend.ModelBackend"] + SESSION_COOKIE_NAME = "OpenSlidesSessionID" SESSION_EXPIRE_AT_BROWSER_CLOSE = True @@ -127,12 +130,5 @@ PASSWORD_HASHERS = [ "django.contrib.auth.hashers.BCryptPasswordHasher", ] - -# Files -# https://docs.djangoproject.com/en/1.10/topics/files/ - -MEDIA_URL = "/media/" - - # Enable updating the last_login field for users on every login. ENABLE_LAST_LOGIN_FIELD = False diff --git a/server/openslides/users/views.py b/server/openslides/users/views.py index 705be34d2..6c43b4a6e 100644 --- a/server/openslides/users/views.py +++ b/server/openslides/users/views.py @@ -5,11 +5,11 @@ from typing import Iterable, List, Set, Union from asgiref.sync import async_to_sync from django.conf import settings from django.contrib.auth import ( + authenticate as auth_authenticate, login as auth_login, logout as auth_logout, update_session_auth_hash, ) -from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import Permission from django.contrib.auth.password_validation import validate_password from django.contrib.auth.tokens import default_token_generator @@ -888,13 +888,19 @@ class UserLoginView(WhoAmIDataView): raise ValidationError( {"detail": "Cookies have to be enabled to use OpenSlides."} ) - form = AuthenticationForm(self.request, data=self.request.data) - if not form.is_valid(): + + username = self.request.data.get("username") + password = self.request.data.get("password") + user = auth_authenticate(self.request, username=username, password=password) + if user is None: raise ValidationError({"detail": "Username or password is not correct."}) - self.user = form.get_user() - if self.user.auth_type != "default": - raise ValidationError({"detail": "Please login via your identity provider"}) - auth_login(self.request, self.user) + elif not user.is_active: + raise ValidationError({"detail": "You are not active."}) + elif user.auth_type != "default": + raise ValidationError( + {"detail": "Please login via your identity provider."} + ) + auth_login(self.request, user) return super().post(*args, **kwargs) def get_context_data(self, **context): diff --git a/server/openslides/utils/auth_backend.py b/server/openslides/utils/auth_backend.py new file mode 100644 index 000000000..c2bebab0f --- /dev/null +++ b/server/openslides/utils/auth_backend.py @@ -0,0 +1,14 @@ +from typing import Any + +from django.contrib.auth.backends import ModelBackend as _ModelBackend + + +class ModelBackend(_ModelBackend): + def user_can_authenticate(self, user: Any) -> bool: + """ + Overwrite the default check for is_active. + This allows us to do the check it later to distinguish between a user + have not the right credentials and having the right credentials but + not being active. + """ + return True diff --git a/server/tests/integration/users/test_views.py b/server/tests/integration/users/test_views.py index 7d02e35a5..e8d6efa11 100644 --- a/server/tests/integration/users/test_views.py +++ b/server/tests/integration/users/test_views.py @@ -3,6 +3,7 @@ import json from django.urls import reverse from rest_framework.test import APIClient +from openslides.users.models import User from tests.test_case import TestCase @@ -101,6 +102,8 @@ class TestUserLoginView(TestCase): response = self.client.post(self.url) self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual(content.get("detail"), "Username or password is not correct.") def test_post_correct_data(self): response = self.client.post( @@ -121,3 +124,41 @@ class TestUserLoginView(TestCase): ) self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual(content.get("detail"), "Username or password is not correct.") + + def test_user_inactive(self): + admin = User.objects.get() + admin.is_active = False + admin.save() + + response = self.client.post( + self.url, {"username": "admin", "password": "admin"} + ) + self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual(content.get("detail"), "You are not active.") + + def test_user_wrong_auth_type(self): + admin = User.objects.get() + admin.auth_type = "not default" + admin.save() + + response = self.client.post( + self.url, {"username": "admin", "password": "admin"} + ) + self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual( + content.get("detail"), "Please login via your identity provider." + ) + + def test_no_cookies(self): + response = self.client.post( + self.url, {"username": "admin", "password": "admin", "cookies": False} + ) + self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual( + content.get("detail"), "Cookies have to be enabled to use OpenSlides." + )