Skip to content

Commit

Permalink
Fix reachability of actions in dev mode (#1818)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsangmeister authored Aug 22, 2023
1 parent 85919cd commit d8511f5
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 22 deletions.
16 changes: 9 additions & 7 deletions openslides_backend/action/action_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
26 changes: 22 additions & 4 deletions tests/system/action/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")


Expand All @@ -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(
Expand All @@ -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(
[
{
Expand All @@ -65,6 +76,7 @@ def request_multi(
],
anonymous=anonymous,
lang=lang,
internal=internal,
)
if response.status_code == 200:
results = response.json.get("results", [])
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tests/system/action/poll/test_vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
22 changes: 12 additions & 10 deletions tests/system/action/test_internal_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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")

0 comments on commit d8511f5

Please sign in to comment.