Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix reachability of actions in dev mode #1818

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")