From 296ac686cccfebc52489b46a931a035e10917ac9 Mon Sep 17 00:00:00 2001 From: jnm Date: Tue, 3 Dec 2024 14:55:40 -0500 Subject: [PATCH] feat(OpenRosa): Block submissions when owning account is inactive TASK-1323 (#5321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 📣 Summary Previously, projects owned by deactivated accounts could still receive submissions, e.g. if the project allowed anonymous submissions, or if a still-active user was granted access to submit to the project. This change categorically prevents all submissions to projects owned by inactive users. ### 👀 Preview steps 1. Log in as a normal user (let's call them "Psy") 2. Create a project and deploy it 3. Set the project to allow anonymous submissions 4. Open Enketo, create a submission, and keep the Enketo tab open 5. In a separate browser session, use a superuser account to deactivate Psy's account (uncheck the "active" box in Django admin) 6. Go back to Enketo and attempt to submit 7. See submission fail with error message "Unknown submission problem on data server. (405)" --- .../viewsets/test_xform_submission_api.py | 41 ++++++++++++++++++- kobo/apps/openrosa/apps/logger/exceptions.py | 4 ++ .../openrosa/apps/logger/models/instance.py | 3 ++ kobo/apps/openrosa/libs/utils/logger_tools.py | 3 ++ 4 files changed, 50 insertions(+), 1 deletion(-) 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 8bc0a561b7..6835cb69ed 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 @@ -18,7 +18,10 @@ from kobo.apps.openrosa.libs.constants import ( CAN_ADD_SUBMISSIONS ) -from kobo.apps.openrosa.libs.utils.logger_tools import OpenRosaTemporarilyUnavailable +from kobo.apps.openrosa.libs.utils.logger_tools import ( + OpenRosaResponseNotAllowed, + OpenRosaTemporarilyUnavailable, +) class TestXFormSubmissionApi(TestAbstractViewSet): @@ -458,6 +461,42 @@ def test_edit_submission_with_service_account(self): response['Location'], 'http://testserver/submission' ) + def test_submission_account_inactive(self): + """ + Verify that submissions are blocked when the owning user has + `is_active = False` + """ + self.xform.user.is_active = False + self.xform.user.save() + + # No need auth for this test + self.xform.require_auth = False + self.xform.save(update_fields=['require_auth']) + + s = self.surveys[0] + username = self.user.username + submission_path = os.path.join( + self.main_directory, + 'fixtures', + 'transportation', + 'instances', + s, + s + '.xml', + ) + with open(submission_path) as sf: + request = self.factory.post( + f'/{username}/submission', {'xml_submission_file': sf} + ) + request.user = AnonymousUser() + + # Ensure that submissions are not accepted since the owning user is + # inactive + response = self.view(request, username=username) + self.assertEqual( + response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED + ) + self.assertTrue(isinstance(response, OpenRosaResponseNotAllowed)) + def test_submission_blocking_flag(self): # Set 'submissions_suspended' True in the profile metadata to test if # submission do fail with the flag set diff --git a/kobo/apps/openrosa/apps/logger/exceptions.py b/kobo/apps/openrosa/apps/logger/exceptions.py index 74b31a19bd..1a51ff666f 100644 --- a/kobo/apps/openrosa/apps/logger/exceptions.py +++ b/kobo/apps/openrosa/apps/logger/exceptions.py @@ -2,6 +2,10 @@ from django.utils.translation import gettext as t +class AccountInactiveError(Exception): + pass + + class DuplicateUUIDError(Exception): pass diff --git a/kobo/apps/openrosa/apps/logger/models/instance.py b/kobo/apps/openrosa/apps/logger/models/instance.py index 77a6d907bc..7a562acc08 100644 --- a/kobo/apps/openrosa/apps/logger/models/instance.py +++ b/kobo/apps/openrosa/apps/logger/models/instance.py @@ -16,6 +16,7 @@ from kobo.apps.kobo_auth.shortcuts import User from kobo.apps.openrosa.apps.logger.exceptions import ( + AccountInactiveError, FormInactiveError, TemporarilyUnavailableError, ) @@ -135,6 +136,8 @@ def check_active(self, force): return if profile.metadata.get('submissions_suspended', False): raise TemporarilyUnavailableError() + if not self.xform.user.is_active: + raise AccountInactiveError() def _set_geom(self): xform = self.xform diff --git a/kobo/apps/openrosa/libs/utils/logger_tools.py b/kobo/apps/openrosa/libs/utils/logger_tools.py index d92ed321fb..dda37aa010 100644 --- a/kobo/apps/openrosa/libs/utils/logger_tools.py +++ b/kobo/apps/openrosa/libs/utils/logger_tools.py @@ -39,6 +39,7 @@ from wsgiref.util import FileWrapper from kobo.apps.openrosa.apps.logger.exceptions import ( + AccountInactiveError, DuplicateUUIDError, FormInactiveError, TemporarilyUnavailableError, @@ -546,6 +547,8 @@ def safe_create_instance(username, xml_file, media_files, uuid, request): error = OpenRosaResponseNotAllowed(t("Form is not active")) except TemporarilyUnavailableError: error = OpenRosaTemporarilyUnavailable(t("Temporarily unavailable")) + except AccountInactiveError: + error = OpenRosaResponseNotAllowed(t('Account is not active')) except XForm.DoesNotExist: error = OpenRosaResponseNotFound( t("Form does not exist on this account")