diff --git a/openslides_backend/action/action_handler.py b/openslides_backend/action/action_handler.py index a9b553566..8a93bb17e 100644 --- a/openslides_backend/action/action_handler.py +++ b/openslides_backend/action/action_handler.py @@ -212,14 +212,16 @@ def perform_action( ) -> Tuple[Optional[WriteRequest], Optional[ActionResults]]: action_name = action_payload_element["action"] ActionClass = actions_map.get(action_name) - if ActionClass is None or ( - not self.env.is_dev_mode() - and ( + # Actions cannot be accessed in the following three cases: + # - they do not exist + # - they are not public and the request is not internal + # - they are backend internal and the backend is not in dev mode + if ( + ActionClass is None + or (ActionClass.action_type != ActionType.PUBLIC and not self.internal) + or ( ActionClass.action_type == ActionType.BACKEND_INTERNAL - or ( - not self.internal - and ActionClass.action_type == ActionType.STACK_INTERNAL - ) + and not self.env.is_dev_mode() ) ): raise View400Exception(f"Action {action_name} does not exist.") diff --git a/tests/system/action/base.py b/tests/system/action/base.py index 1d05f913c..e4125f9b2 100644 --- a/tests/system/action/base.py +++ b/tests/system/action/base.py @@ -5,6 +5,7 @@ from openslides_backend.action.action_worker import gunicorn_post_request from openslides_backend.action.relations.relation_manager import RelationManager +from openslides_backend.action.util.action_type import ActionType from openslides_backend.action.util.actions_map import actions_map from openslides_backend.action.util.crypto import get_random_string from openslides_backend.action.util.typing import ActionResults, Payload @@ -20,6 +21,7 @@ from openslides_backend.shared.patterns import FullQualifiedId from openslides_backend.shared.typing import HistoryInformation from openslides_backend.shared.util import ONE_ORGANIZATION_FQID +from tests.system.action.util import get_internal_auth_header from tests.system.base import BaseSystemTestCase from tests.system.util import create_action_test_application, get_route_path from tests.util import Response @@ -28,6 +30,7 @@ DEFAULT_PASSWORD = "password" ACTION_URL = get_route_path(ActionView.action_route) +ACTION_URL_INTERNAL = get_route_path(ActionView.internal_action_route) ACTION_URL_SEPARATELY = get_route_path(ActionView.action_route, "handle_separately") @@ -41,12 +44,14 @@ def request( data: Dict[str, Any], anonymous: bool = False, lang: Optional[str] = None, + internal: Optional[bool] = None, ) -> Response: return self.request_multi( action, [data], anonymous=anonymous, lang=lang, + internal=internal, ) def request_multi( @@ -55,7 +60,13 @@ def request_multi( data: List[Dict[str, Any]], anonymous: bool = False, lang: Optional[str] = None, + internal: Optional[bool] = None, ) -> Response: + ActionClass = actions_map.get(action) + if internal is None: + internal = bool( + ActionClass and ActionClass.action_type != ActionType.PUBLIC + ) response = self.request_json( [ { @@ -65,6 +76,7 @@ def request_multi( ], anonymous=anonymous, lang=lang, + internal=internal, ) if response.status_code == 200: results = response.json.get("results", []) @@ -77,22 +89,28 @@ def request_json( payload: Payload, anonymous: bool = False, lang: Optional[str] = None, + internal: bool = False, atomic: bool = True, ) -> Response: client = self.client if not anonymous else self.anon_client headers = {} if lang: headers["Accept-Language"] = lang - if atomic: + if internal and atomic: + url = ACTION_URL_INTERNAL + headers.update(get_internal_auth_header()) + elif atomic: url = ACTION_URL - else: + elif not internal: url = ACTION_URL_SEPARATELY + else: + raise NotImplementedError("Cannot send internal non-atomic requests.") response = client.post(url, json=payload, headers=headers) if response.status_code == 202: gunicorn_post_request( MockGunicornThreadWorker(), - None, # type:ignore - None, # type:ignore + None, # type: ignore + None, # type: ignore response, ) return response diff --git a/tests/system/action/poll/test_vote.py b/tests/system/action/poll/test_vote.py index d0aef175c..dd8c64644 100644 --- a/tests/system/action/poll/test_vote.py +++ b/tests/system/action/poll/test_vote.py @@ -17,6 +17,7 @@ def request( data: Dict[str, Any], anonymous: bool = False, lang: Optional[str] = None, + internal: Optional[bool] = None, start_poll_before_vote: bool = True, stop_poll_after_vote: bool = True, ) -> Response: @@ -29,7 +30,7 @@ def request( self.execute_action_internally("poll.stop", {"id": data["id"]}) return response else: - return super().request(action, data, anonymous, lang) + return super().request(action, data, anonymous, lang, internal) def anonymous_vote(self, payload: Dict[str, Any], id: int = 1) -> Response: # make request manually to prevent sending of cookie & header diff --git a/tests/system/action/test_internal_actions.py b/tests/system/action/test_internal_actions.py index d026e9606..cc7ffd013 100644 --- a/tests/system/action/test_internal_actions.py +++ b/tests/system/action/test_internal_actions.py @@ -134,6 +134,18 @@ def test_internal_wrong_password_in_request(self) -> None: self.assert_status_code(response, 401) self.assert_model_not_exists("user/2") + def test_internal_execute_stack_internal_via_public_route(self) -> None: + self.datastore.truncate_db() + response = self.request( + "organization.initial_import", {"data": {}}, internal=False + ) + self.assert_status_code(response, 400) + self.assertEqual( + response.json.get("message"), + "Action organization.initial_import does not exist.", + ) + self.assert_model_not_exists("organization/1") + def test_internal_wrongly_encoded_password(self) -> None: response = self.anon_client.post( get_route_path(self.route), @@ -197,13 +209,3 @@ def test_internal_execute_backend_internal_action(self) -> None: response.json.get("message"), "Action option.create does not exist." ) self.assert_model_not_exists("option/1") - - def test_internal_execute_stack_internal_via_public_route(self) -> None: - self.datastore.truncate_db() - response = self.request("organization.initial_import", {"data": {}}) - self.assert_status_code(response, 400) - self.assertEqual( - response.json.get("message"), - "Action organization.initial_import does not exist.", - ) - self.assert_model_not_exists("organization/1")