Fix anonymous user for rest requests

This commit is contained in:
Oskar Hahn 2015-05-05 10:42:31 +02:00
parent a033f74ba1
commit cb1b262c92
12 changed files with 129 additions and 132 deletions

View File

@ -163,7 +163,7 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
Return the title of this item. Return the title of this item.
""" """
if not self.content_object: if not self.content_object:
agenda_title = self.title agenda_title = self.title or ""
else: else:
try: try:
agenda_title = self.content_object.get_agenda_title() agenda_title = self.content_object.get_agenda_title()

View File

@ -183,5 +183,8 @@ TEST_RUNNER = 'openslides.utils.test.OpenSlidesDiscoverRunner'
# Config for the REST Framework # Config for the REST Framework
REST_FRAMEWORK = { REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': 'openslides.users.auth.AnonymousUser', 'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'openslides.users.auth.AnonymousAuthentication',
)
} }

View File

@ -1,15 +1,15 @@
from django.contrib.auth import get_user as _get_user
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
from django.contrib.auth.context_processors import auth as _auth from django.contrib.auth.context_processors import auth as _auth
from django.contrib.auth import get_user as _get_user from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
from django.contrib.auth.models import Permission
from django.db.models import Q from django.db.models import Q
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from rest_framework.authentication import BaseAuthentication
from openslides.config.api import config from openslides.config.api import config
from .models import Permission
class AnonymousUser(DjangoAnonymousUser): class AnonymousUser(DjangoAnonymousUser):
""" """
@ -94,6 +94,20 @@ class AuthenticationMiddleware(object):
request.user = SimpleLazyObject(lambda: get_user(request)) request.user = SimpleLazyObject(lambda: get_user(request))
class AnonymousAuthentication(BaseAuthentication):
"""
Authentication class for the Django REST framework.
Sets the user to the our AnonymousUser but only if system_enable_anonymous
is set to True in the config.
"""
def authenticate(self, request):
if config['system_enable_anonymous']:
return (AnonymousUser(), None)
return None
def get_user(request): def get_user(request):
""" """
Gets the user from the request. Gets the user from the request.

View File

@ -99,7 +99,7 @@ class User(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, PermissionsMixin, Abstr
slide_callback_name = 'user' slide_callback_name = 'user'
username = models.CharField( username = models.CharField(
ugettext_lazy('Username'), max_length=255, unique=True) ugettext_lazy('Username'), max_length=255, unique=True, blank=True)
first_name = models.CharField( first_name = models.CharField(
ugettext_lazy('First name'), max_length=255, blank=True) ugettext_lazy('First name'), max_length=255, blank=True)

View File

@ -1,4 +1,3 @@
from django.core.exceptions import ImproperlyConfigured
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
@ -31,32 +30,6 @@ class UserFullSerializer(ModelSerializer):
Serializes all relevant fields. Serializes all relevant fields.
""" """
class Meta:
model = User
fields = (
'id',
'is_present',
'username',
'title',
'first_name',
'last_name',
'structure_level',
'about_me',
'comment',
'groups',
'default_password',
'last_login',
'is_active',)
class UserCreateUpdateSerializer(ModelSerializer):
"""
Serializer for users.models.User objects.
Serializes data to create new users or update users.
Do not use this for list or retrieve requests.
"""
groups = PrimaryKeyRelatedField( groups = PrimaryKeyRelatedField(
many=True, many=True,
queryset=Group.objects.exclude(pk__in=(1, 2)), queryset=Group.objects.exclude(pk__in=(1, 2)),
@ -80,37 +53,33 @@ class UserCreateUpdateSerializer(ModelSerializer):
'default_password', 'default_password',
'is_active',) 'is_active',)
def __init__(self, *args, **kwargs):
"""
Overridden to add read_only flag to username field in create requests.
"""
super().__init__(*args, **kwargs)
if self.context['view'].action == 'create':
self.fields['username'].read_only = True
elif self.context['view'].action == 'update':
# Everything is fine. Do nothing.
pass
else: # Other action than 'create' or 'update'.
raise ImproperlyConfigured('This serializer can only be used in create and update requests.')
def validate(self, data): def validate(self, data):
""" """
Checks that first_name or last_name is given. Checks that first_name or last_name is given.
Generates the username if it is empty.
""" """
if not (data.get('username') or data.get('first_name') or data.get('last_name')): if not (data.get('username') or data.get('first_name') or data.get('last_name')):
raise ValidationError(_('Username, first name and last name can not all be empty.')) raise ValidationError(_('Username, first name and last name can not all be empty.'))
# Generate username. But only if it is not set and the serializer is not
# called in a patch-context.
try:
action = self.context['view'].action
except (KeyError, AttributeError):
action = None
if not data.get('username') and action != 'partial_update':
data['username'] = User.objects.generate_username(
data.get('first_name', ''),
data.get('last_name', ''))
return data return data
def create(self, validated_data): def create(self, validated_data):
""" """
Creates user with generated username and sets the default_password. Creates the user. Sets the default_password. Adds the new user to the
Adds the new user to the registered group. registered group.
""" """
# Generate username if neccessary.
if not validated_data.get('username'):
validated_data['username'] = User.objects.generate_username(
validated_data.get('first_name', ''),
validated_data.get('last_name', ''))
# Prepare setup password. # Prepare setup password.
if not validated_data.get('default_password'): if not validated_data.get('default_password'):
validated_data['default_password'] = User.objects.generate_password() validated_data['default_password'] = User.objects.generate_password()

View File

@ -22,7 +22,6 @@ from .models import Group, User
from .pdf import users_passwords_to_pdf, users_to_pdf from .pdf import users_passwords_to_pdf, users_to_pdf
from .serializers import ( from .serializers import (
GroupSerializer, GroupSerializer,
UserCreateUpdateSerializer,
UserFullSerializer, UserFullSerializer,
UserShortSerializer, UserShortSerializer,
) )
@ -88,9 +87,8 @@ class UserViewSet(ModelViewSet):
Returns different serializer classes with respect to action and user's Returns different serializer classes with respect to action and user's
permissions. permissions.
""" """
if self.action in ('create', 'update'): if (self.action in ('create', 'update') or
serializer_class = UserCreateUpdateSerializer self.request.user.has_perm('users.can_see_extra_data')):
elif self.request.user.has_perm('users.can_see_extra_data'):
serializer_class = UserFullSerializer serializer_class = UserFullSerializer
else: else:
serializer_class = UserShortSerializer serializer_class = UserShortSerializer

View File

@ -5,7 +5,7 @@ bleach>=1.4,<1.5
django-ckeditor-updated>=4.2.3,<4.4 django-ckeditor-updated>=4.2.3,<4.4
django-haystack>=2.1,<2.4 django-haystack>=2.1,<2.4
django-mptt>=0.6,<0.7 django-mptt>=0.6,<0.7
djangorestframework>=3.0.5,<3.1.0 djangorestframework>=3.0.5,<3.2.0
jsonfield>=0.9.19,<1.1 jsonfield>=0.9.19,<1.1
natsort>=3.2,<3.6 natsort>=3.2,<3.6
reportlab>=3.0,<3.2 reportlab>=3.0,<3.2

View File

@ -10,4 +10,4 @@ max_line_length = 150
[isort] [isort]
include_trailing_comma = true include_trailing_comma = true
multi_line_output = 3 multi_line_output = 3
skip = main.py,signals.py,csv_import.py,global_settings.py,test_personal_info.py,tests.py,auth.py,search_indexes.py,test_list_of_speakers.py,chatbox.py,test_main_menu.py,test_config.py,runserver.py,rest_api.py,settings.py,test_pdf.py,__main__.py,test_serializers.py,autoupdate.py,pdf.py,widgets.py,models.py,test_csv_import.py,plugins.py,serializers.py,test_csv.py,test_overlays.py,backupdb.py,views.py,test_views.py,urls.py,forms.py,utils.py,test_auth.py skip = main.py,signals.py,csv_import.py,global_settings.py,test_personal_info.py,tests.py,search_indexes.py,test_list_of_speakers.py,chatbox.py,test_main_menu.py,test_config.py,runserver.py,rest_api.py,settings.py,test_pdf.py,__main__.py,autoupdate.py,pdf.py,widgets.py,models.py,test_csv_import.py,plugins.py,serializers.py,test_csv.py,test_overlays.py,backupdb.py,views.py,test_views.py,urls.py,forms.py,utils.py

View File

@ -0,0 +1,25 @@
from unittest.mock import patch
from openslides.utils.test import TestCase
class TestAnonymousRequests(TestCase):
"""
Test that a request with an user that is not logged in gets only the
requested data, if the anonymous user is activated in the config.
Expects that the page '/rest/users/user/' needs a permission and the
anonymous user has this permission.
"""
@patch('openslides.users.auth.config', {'system_enable_anonymous': True})
def test_with_anonymous_user(self):
response = self.client.get('/rest/users/user/')
self.assertEqual(response.status_code, 200)
@patch('openslides.users.auth.config', {'system_enable_anonymous': False})
def test_without_anonymous_user(self):
response = self.client.get('/rest/users/user/')
self.assertEqual(response.status_code, 403)

View File

@ -6,6 +6,25 @@ from openslides.users.models import Group, User
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
class UserGetTest(TestCase):
"""
Tests to receive a users via REST API.
"""
def test_get_with_user_who_is_in_group_with_pk_1(self):
"""
It is invalid, that a user is in the group with the pk 1. But if the
database is invalid, the user should nevertheless be received.
"""
admin = User.objects.get(pk=1)
group1 = Group.objects.get(pk=1)
admin.groups.add(group1)
self.client.login(username='admin', password='admin')
response = self.client.get('/rest/users/user/1/')
self.assertEqual(response.status_code, 200)
class UserCreate(TestCase): class UserCreate(TestCase):
""" """
Tests creation of users via REST API. Tests creation of users via REST API.
@ -47,7 +66,7 @@ class UserCreate(TestCase):
'groups': group_pks}) 'groups': group_pks})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'groups': ["Invalid pk '%d' - object does not exist." % group_pks[0]]}) self.assertEqual(response.data, {'groups': ["Invalid pk \"%d\" - object does not exist." % group_pks[0]]})
class UserUpdate(TestCase): class UserUpdate(TestCase):
@ -55,6 +74,11 @@ class UserUpdate(TestCase):
Tests update of users via REST API. Tests update of users via REST API.
""" """
def test_simple_update_via_patch(self): def test_simple_update_via_patch(self):
"""
Test to only update the last_name with a patch request.
The field username *should not* be changed by the request.
"""
admin_client = APIClient() admin_client = APIClient()
admin_client.login(username='admin', password='admin') admin_client.login(username='admin', password='admin')
# This is the builtin user 'Administrator' with username 'admin'. The pk is valid. # This is the builtin user 'Administrator' with username 'admin'. The pk is valid.
@ -70,6 +94,11 @@ class UserUpdate(TestCase):
self.assertEqual(user.username, 'admin') self.assertEqual(user.username, 'admin')
def test_simple_update_via_put(self): def test_simple_update_via_put(self):
"""
Test to only update the last_name with a put request.
The field username *should* be changed by the request.
"""
admin_client = APIClient() admin_client = APIClient()
admin_client.login(username='admin', password='admin') admin_client.login(username='admin', password='admin')
# This is the builtin user 'Administrator'. The pk is valid. # This is the builtin user 'Administrator'. The pk is valid.
@ -77,10 +106,10 @@ class UserUpdate(TestCase):
response = admin_client.put( response = admin_client.put(
reverse('user-detail', args=[user_pk]), reverse('user-detail', args=[user_pk]),
{'last_name': 'New name Ohy4eeyei5Sahzah0Os2'}) {'last_name': 'New name Ohy4eeyei5'})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, {'username': ['This field is required.']}) self.assertEqual(User.objects.get(pk=1).username, 'New name Ohy4eeyei5')
class UserDelete(TestCase): class UserDelete(TestCase):

View File

@ -1,7 +1,7 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import MagicMock, patch
from openslides.users.auth import AnonymousUser, get_user, auth from openslides.users.auth import AnonymousUser, auth, get_user
class TestAnonymousUser(TestCase): class TestAnonymousUser(TestCase):

View File

@ -1,83 +1,42 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from django.core.exceptions import ImproperlyConfigured from openslides.users.serializers import UserFullSerializer
from rest_framework import status from openslides.utils.rest_api import ValidationError
from rest_framework.viewsets import ModelViewSet
from rest_framework.test import APIRequestFactory
class UserCreateUpdateSerializer(TestCase): class UserCreateUpdateSerializerTest(TestCase):
def test_improperly_configured_exception_list_request(self): def test_validate_no_data(self):
""" """
Tests that ImproperlyConfigured is raised if one tries to use the Tests, that the validator raises a ValidationError, if not data is given.
UserCreateUpdateSerializer with a list request.
""" """
# Global import is not possible for some unknown magic. serializer = UserFullSerializer()
from openslides.users.serializers import UserCreateUpdateSerializer data = {}
factory = APIRequestFactory()
request = factory.get('/')
view_class = ModelViewSet
view_class.queryset = MagicMock()
view_class.serializer_class = UserCreateUpdateSerializer
view = view_class.as_view({'get': 'list'})
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ValidationError):
view(request) serializer.validate(data)
@patch('rest_framework.generics.get_object_or_404') @patch('openslides.users.serializers.User.objects.generate_username')
def test_improperly_configured_exception_retrieve_request(self, mock_get_object_or_404): def test_validate_no_username(self, generate_username):
""" """
Tests that ImproperlyConfigured is raised if one tries to use the Tests, that an empty username is generated.
UserCreateUpdateSerializer with a retrieve request.
""" """
# Global import is not possible for some unknown magic. generate_username.return_value = 'test_value'
from openslides.users.serializers import UserCreateUpdateSerializer serializer = UserFullSerializer()
factory = APIRequestFactory() data = {'first_name': 'TestName'}
request = factory.get('/')
view_class = ModelViewSet
view_class.queryset = MagicMock()
view_class.serializer_class = UserCreateUpdateSerializer
view = view_class.as_view({'get': 'retrieve'})
mock_get_object_or_404.return_value = MagicMock()
with self.assertRaises(ImproperlyConfigured): new_data = serializer.validate(data)
view(request, pk='1')
def test_no_improperly_configured_exception_create_request(self): self.assertEqual(new_data['username'], 'test_value')
def test_validate_no_username_in_patch_request(self):
""" """
Tests that ImproperlyConfigured is not raised if one tries to use the Tests, that an empty username is not set in a patch request context.
UserCreateUpdateSerializer with a create request.
""" """
# Global import is not possible for some unknown magic. view = MagicMock(action='partial_update')
from openslides.users.serializers import UserCreateUpdateSerializer serializer = UserFullSerializer(context={'view': view})
factory = APIRequestFactory() data = {'first_name': 'TestName'}
request = factory.get('/')
view_class = ModelViewSet
view_class.queryset = MagicMock()
view_class.serializer_class = UserCreateUpdateSerializer
view = view_class.as_view({'get': 'create'})
response = view(request) new_data = serializer.validate(data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIsNone(new_data.get('username'))
@patch('rest_framework.generics.get_object_or_404')
def test_no_improperly_configured_exception_update_request(self, mock_get_object_or_404):
"""
Tests that ImproperlyConfigured is not raised if one tries to use the
UserCreateUpdateSerializer with a update request.
"""
# Global import is not possible for some unknown magic.
from openslides.users.serializers import UserCreateUpdateSerializer
factory = APIRequestFactory()
request = factory.get('/')
view_class = ModelViewSet
view_class.queryset = MagicMock()
view_class.serializer_class = UserCreateUpdateSerializer
view = view_class.as_view({'get': 'update'})
mock_get_object_or_404.return_value = MagicMock()
response = view(request, pk='1')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)