From 828bf465fa192e28b27c74835c0d7cd0688f097e Mon Sep 17 00:00:00 2001 From: Raj Patel Date: Thu, 30 Oct 2025 16:14:46 +0530 Subject: [PATCH 1/3] Handle empty submission file --- kobo/apps/openrosa/libs/utils/logger_tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kobo/apps/openrosa/libs/utils/logger_tools.py b/kobo/apps/openrosa/libs/utils/logger_tools.py index 27be31438a..817596b041 100644 --- a/kobo/apps/openrosa/libs/utils/logger_tools.py +++ b/kobo/apps/openrosa/libs/utils/logger_tools.py @@ -200,6 +200,8 @@ def create_instance( processed. PermissionDenied: If the submission fails permission checks. """ + if not xml_file: + raise InstanceEmptyError() if username: username = username.lower() From 71408d9a86981757173a9dc7ff9e7eed1bf1f416 Mon Sep 17 00:00:00 2001 From: Raj Patel Date: Mon, 17 Nov 2025 21:17:47 +0530 Subject: [PATCH 2/3] Update logic and add unit test --- .../viewsets/test_xform_submission_api.py | 31 +++++++++++++++++++ .../apps/api/viewsets/xform_submission_api.py | 7 +++++ kobo/apps/openrosa/libs/utils/logger_tools.py | 2 -- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py b/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py index 5bae046afd..2293d5e15d 100644 --- a/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py +++ b/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py @@ -921,6 +921,37 @@ def test_submission_data_collector(self): f'http://testserver/collector/{dc.token}/submission', ) + def test_digest_auth_allows_submission_on_username_endpoint(self): + """ + Test that Digest authentication works correctly on the + `//submission` endpoint when the xform requires auth + """ + username = self.user.username + + # Ensure that POST to `//submission` fails without auth + request = self.factory.post(f'/{username}/submission') + response = self.view(request, username=username) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + # Ensure that POST to `//submission` with Digest auth + s = self.surveys[0] + submission_path = os.path.join( + self.main_directory, 'fixtures', + 'transportation', 'instances', s, s + '.xml' + ) + with open(submission_path) as sf: + data = {'xml_submission_file': sf} + request = self.factory.post(f'/{username}/submission', data={}) + response = self.view(request, username=username) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + sf.seek(0) + request = self.factory.post(f'/{username}/submission', data) + auth = DigestAuth('bob', 'bobbob') + request.META.update(auth(request.META, response)) + response = self.view(request, username=username) + self.assertContains(response, 'Successful submission', status_code=201) + class ConcurrentSubmissionTestCase(RequestMixin, LiveServerTestCase): """ diff --git a/kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py b/kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py index 426ed55ad8..5309ccb9e2 100644 --- a/kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py +++ b/kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py @@ -5,6 +5,7 @@ from django.utils.translation import gettext as t from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import mixins, permissions, status +from rest_framework.authentication import get_authorization_header from rest_framework.decorators import action from rest_framework.exceptions import NotAuthenticated from rest_framework.parsers import FormParser, JSONParser @@ -249,6 +250,12 @@ def create(self, request, *args, **kwargs): user = get_database_user(request.user) username = user.username + # Return 401 if no authentication provided and there are no files, + # for digest authentication to work properly + has_auth = bool(get_authorization_header(request)) + if not has_auth and not (bool(request.FILES) or bool(request.data)): + raise NotAuthenticated + if request.method.upper() == 'HEAD': return Response( status=status.HTTP_204_NO_CONTENT, diff --git a/kobo/apps/openrosa/libs/utils/logger_tools.py b/kobo/apps/openrosa/libs/utils/logger_tools.py index c605861370..e095a02067 100644 --- a/kobo/apps/openrosa/libs/utils/logger_tools.py +++ b/kobo/apps/openrosa/libs/utils/logger_tools.py @@ -198,8 +198,6 @@ def create_instance( processed. PermissionDenied: If the submission fails permission checks. """ - if not xml_file: - raise InstanceEmptyError() if username: username = username.lower() From 141d66262bf4eff9d495b272d4203248222dd401 Mon Sep 17 00:00:00 2001 From: Raj Patel Date: Fri, 21 Nov 2025 18:44:49 +0530 Subject: [PATCH 3/3] Update unit test --- .../apps/api/tests/viewsets/test_xform_submission_api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py b/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py index 2293d5e15d..7114ef9d79 100644 --- a/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py +++ b/kobo/apps/openrosa/apps/api/tests/viewsets/test_xform_submission_api.py @@ -940,17 +940,20 @@ def test_digest_auth_allows_submission_on_username_endpoint(self): 'transportation', 'instances', s, s + '.xml' ) with open(submission_path) as sf: - data = {'xml_submission_file': sf} request = self.factory.post(f'/{username}/submission', data={}) response = self.view(request, username=username) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - sf.seek(0) + data = {'xml_submission_file': sf} + request = self.factory.post(f'/{username}/submission', data) auth = DigestAuth('bob', 'bobbob') request.META.update(auth(request.META, response)) + response = self.view(request, username=username) - self.assertContains(response, 'Successful submission', status_code=201) + self.assertContains( + response, 'Successful submission', status_code=status.HTTP_201_CREATED + ) class ConcurrentSubmissionTestCase(RequestMixin, LiveServerTestCase):