diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 254b64dcb..0bacd6dd2 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,7 +2,6 @@ name: CI on: pull_request: - branches: [master] jobs: test-server: diff --git a/client/src/app/shared/components/video-player/video-player.component.ts b/client/src/app/shared/components/video-player/video-player.component.ts index ef2f443ea..4d28fbff9 100644 --- a/client/src/app/shared/components/video-player/video-player.component.ts +++ b/client/src/app/shared/components/video-player/video-player.component.ts @@ -109,7 +109,7 @@ export class VideoPlayerComponent implements AfterViewInit, OnDestroy { } public get nanocosmosVideoUrl(): string { - return `https://demo.nanocosmos.de/nanoplayer/embed/1.0.0/nanoplayer.html?entry.rtmp.streamname=${this.videoId}`; + return `https://demo.nanocosmos.de/nanoplayer/embed/1.0.0/nanoplayer.html?entry.rtmp.streamname=${this.videoId}`; // tslint:disable } public constructor( @@ -237,7 +237,7 @@ export class VideoPlayerComponent implements AfterViewInit, OnDestroy { } private getNanocosmosVideoId(url: string): string { - const urlParts: Array = url.split('='); + const urlParts: String[] = url.split('='); if (urlParts?.length && typeof urlParts[1] === 'string') { return urlParts[1]; } diff --git a/server/openslides/users/urls.py b/server/openslides/users/urls.py index 3314a5cde..cd62fc40e 100644 --- a/server/openslides/users/urls.py +++ b/server/openslides/users/urls.py @@ -20,4 +20,9 @@ urlpatterns = [ views.PasswordResetConfirmView.as_view(), name="password_reset_confirm", ), + url( + r"^get-user/$", + views.GetUserView.as_view(), + name="get_user", + ), ] diff --git a/server/openslides/users/views.py b/server/openslides/users/views.py index 95583c7a5..0856fcb32 100644 --- a/server/openslides/users/views.py +++ b/server/openslides/users/views.py @@ -39,6 +39,7 @@ from ..utils.autoupdate import AutoupdateElement, inform_changed_data, inform_el from ..utils.cache import element_cache from ..utils.rest_api import ( ModelViewSet, + NotFound, Response, SimpleMetadata, ValidationError, @@ -1168,3 +1169,61 @@ class PasswordResetConfirmView(APIView): except (TypeError, ValueError, OverflowError, User.DoesNotExist): user = None return user + + +class GetUserView(APIView): + """ + View to retrieve a single user. + + Use query parameters "id", "username", "email" or "number" to look for + a user and retrieve its data. + """ + + http_method_names = ["get"] + + def get(self, request, *args, **kwargs): + # Check permissions. + if not request.user.is_authenticated or not has_perm( + request.user, "users.can_manage" + ): + self.permission_denied(request) + + # Parse parameters + params = request.GET + user_id = params.get("id", "0") + try: + user_id = int(user_id) + except ValueError: + raise ValidationError({"detail": "Invalid parameter user_id."}) + email = params.get("email", "") + username = params.get("username", "") + number = params.get("number", "") + + # Build queryset + query = User.objects.all() + if user_id: + query = query.filter(pk=user_id) + if username: + query = query.filter(username=username) + if email: + query = query.filter(email=email) + if number: + query = query.filter(number=number) + + # Execute queryset + try: + self.user = query.get() + except User.DoesNotExist: + raise NotFound({"detail": "User does not exist."}) + except User.MultipleObjectsReturned: + raise ValidationError({"detail": "Found more than one user."}) + + return super().get(request, *args, **kwargs) + + def get_context_data(self, **context): + user_full_data = async_to_sync(element_cache.get_element_data)( + self.user.get_collection_string(), self.user.pk + ) + user_full_data.pop("session_auth_hash") + context.update({"user": user_full_data}) + return context diff --git a/server/openslides/utils/rest_api.py b/server/openslides/utils/rest_api.py index 20b1505f7..9dcb34261 100644 --- a/server/openslides/utils/rest_api.py +++ b/server/openslides/utils/rest_api.py @@ -4,7 +4,7 @@ from typing import Any, Dict, Iterable, Type from django.db.models import Model from rest_framework import status from rest_framework.decorators import action -from rest_framework.exceptions import APIException +from rest_framework.exceptions import APIException, NotFound, ValidationError from rest_framework.metadata import SimpleMetadata from rest_framework.mixins import ( CreateModelMixin as _CreateModelMixin, @@ -33,7 +33,6 @@ from rest_framework.serializers import ( Serializer, SerializerMetaclass, SerializerMethodField, - ValidationError, ) from rest_framework.utils.serializer_helpers import ReturnDict from rest_framework.viewsets import GenericViewSet as _GenericViewSet @@ -59,6 +58,7 @@ __all__ = [ "RelatedField", "SerializerMethodField", "ValidationError", + "NotFound", ] diff --git a/server/tests/integration/users/test_views.py b/server/tests/integration/users/test_views.py index 59fbe13e1..e108470a1 100644 --- a/server/tests/integration/users/test_views.py +++ b/server/tests/integration/users/test_views.py @@ -162,3 +162,70 @@ class TestUserLoginView(TestCase): self.assertEqual( content.get("detail"), "Cookies have to be enabled to use OpenSlides." ) + + +class TestGetUserView(TestCase): + url = reverse("get_user") + + def setUp(self): + pass + + def test_get_anonymous(self): + response = self.client.get(self.url) + + self.assertEqual(response.status_code, 403) + content = json.loads(response.content.decode()) + self.assertEqual( + content.get("detail"), "Authentication credentials were not provided." + ) + + def test_get_authenticated_user(self): + self.client.login(username="admin", password="admin") + + response = self.client.get(self.url, {"username": "admin"}) + + self.assertEqual(response.status_code, 200) + user = json.loads(response.content.decode()).get("user") + self.assertEqual(user["username"], "admin") + self.assertEqual(user["last_name"], "Administrator") + + def test_post(self): + response = self.client.post(self.url) + + self.assertEqual(response.status_code, 405) + + def test_not_found(self): + self.client.login(username="admin", password="admin") + + response = self.client.get(self.url, {"username": "not-existing-username"}) + + self.assertEqual(response.status_code, 404) + content = json.loads(response.content.decode()) + self.assertEqual(content.get("detail"), "User does not exist.") + + def test_multiple_objects(self): + self.client.login(username="admin", password="admin") + u1, p1 = self.create_user() + u1.number = "Number#1234567890" + u1.save() + u2, p2 = self.create_user() + u2.number = "Number#1234567890" + u2.save() + + response = self.client.get(self.url, {"number": "Number#1234567890"}) + + self.assertEqual(response.status_code, 400) + content = json.loads(response.content.decode()) + self.assertEqual(content.get("detail"), "Found more than one user.") + + def test_delegate(self): + self.make_admin_delegate() + self.client.login(username="admin", password="admin") + + response = self.client.get(self.url, {"username": "admin"}) + + self.assertEqual(response.status_code, 403) + content = json.loads(response.content.decode()) + self.assertEqual( + content.get("detail"), "You do not have permission to perform this action." + )