Skip to content

Commit

Permalink
Forbid committee admins from cloning foreign non-template meetings (#…
Browse files Browse the repository at this point in the history
…2540)

* Forbid committee admins from cloning foreign non-template meetings

* Change wiki

* Change confusing part to more accurate description of function

* Remove unnecessary second committee changes from test
  • Loading branch information
luisa-beerboom authored Aug 8, 2024
1 parent b9d51b6 commit bf23eb5
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 14 deletions.
11 changes: 8 additions & 3 deletions docs/actions/meeting.clone.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ The users in user_ids/admin_ids will also be added to the default_group/admin_gr
A differing committee_id can be given, otherwise the committee_id
will be cloned untouched.

It has to be checked, whether the organization.limit_of_meetings is unlimited(=0) or lower than the active meetings in organization.active_meeting_ids, if the new meeting is not archived (`is_active_in_organization_id` is set)
If an archived meeting is cloned, the created meeting will be active.

It has to be checked, whether the organization.limit_of_meetings is unlimited(=0) or lower than the active meetings in organization.active_meeting_ids.

Meetings that have `locked_from_inside` set to true can not be cloned.

Expand All @@ -37,5 +39,8 @@ If set_as_template is given, template_for_organization_id has to be set to 1.

## Permission

The request user must have the CML `can_manage` in the target committee (where the meeting is created).
If the organization setting `require_duplicate_from` is set, a committee manager can only clone template meetings.
If a meeting from a different committee is being cloned and said meeting isn't a template, the request user needs the OML `organization.can_manage`

Otherwise the request user only needs the CML `can_manage` in the target committee (where the meeting is created).

If the organization setting `require_duplicate_from` is set, a committee manager can only clone template meetings.
20 changes: 19 additions & 1 deletion openslides_backend/action/actions/meeting/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
)
from openslides_backend.models.models import Meeting, MeetingUser
from openslides_backend.services.datastore.interface import GetManyRequest
from openslides_backend.shared.exceptions import ActionException
from openslides_backend.shared.exceptions import ActionException, MissingPermission
from openslides_backend.shared.interfaces.event import Event, EventType
from openslides_backend.shared.patterns import fqid_from_collection_and_id
from openslides_backend.shared.schema import id_list_schema, required_id_schema
Expand Down Expand Up @@ -79,6 +79,24 @@ def preprocess_data(self, instance: dict[str, Any]) -> dict[str, Any]:
return instance

def check_permissions(self, instance: dict[str, Any]) -> None:
if "committee_id" in instance:
meeting = self.datastore.get(
fqid_from_collection_and_id("meeting", instance["meeting_id"]),
["committee_id", "template_for_organization_id"],
lock_result=False,
)
if (
meeting["committee_id"] != instance["committee_id"]
and not meeting.get("template_for_organization_id")
and not has_organization_management_level(
self.datastore,
self.user_id,
OrganizationManagementLevel.CAN_MANAGE_ORGANIZATION,
)
):
raise MissingPermission(
OrganizationManagementLevel.CAN_MANAGE_ORGANIZATION
)
MeetingPermissionMixin.check_permissions(self, instance)

def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
Expand Down
60 changes: 50 additions & 10 deletions tests/system/action/meeting/test_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,35 +1285,37 @@ def test_meeting_name_too_long(self) -> None:
self.assert_status_code(response, 200)
self.assert_model_exists("meeting/2", {"name": "A" * 90 + "... - Copy"})

def test_permissions_both_okay(self) -> None:
def test_permissions_explicit_source_committee_permission(self) -> None:
self.set_models(self.test_models)
self.set_models(
{
"committee/2": {"organization_id": 1},
"user/1": {
"committee_management_ids": [1, 2],
"committee_ids": [1, 2],
"committee_management_ids": [1],
"committee_ids": [1],
"organization_management_level": None,
},
}
)
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 2})
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 1})
self.assert_status_code(response, 200)
self.assert_model_exists(
"meeting/1", {"is_active_in_organization_id": 1, "committee_id": 1}
)
self.assert_model_exists(
"meeting/2", {"is_active_in_organization_id": 1, "committee_id": 2}
"meeting/2", {"is_active_in_organization_id": 1, "committee_id": 1}
)

def test_permissions_oml_can_manage(self) -> None:
def test_permissions_foreign_template_meeting_cml(self) -> None:
self.set_models(self.test_models)
self.set_models(
{
"committee/2": {"organization_id": 1},
"user/1": {
"organization_management_level": "can_manage_organization",
"committee_management_ids": [1, 2],
"committee_ids": [2],
"organization_management_level": None,
},
"meeting/1": {"template_for_organization_id": 1},
}
)
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 2})
Expand All @@ -1325,7 +1327,26 @@ def test_permissions_oml_can_manage(self) -> None:
"meeting/2", {"is_active_in_organization_id": 1, "committee_id": 2}
)

def test_permissions_missing_payload_committee_permission(self) -> None:
def test_permissions_foreign_meeting_cml_error(self) -> None:
self.set_models(self.test_models)
self.set_models(
{
"committee/2": {"organization_id": 1},
"user/1": {
"committee_management_ids": [1, 2],
"committee_ids": [1, 2],
"organization_management_level": None,
},
}
)
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 2})
self.assert_status_code(response, 403)
self.assertIn(
"You are not allowed to perform action meeting.clone. Missing OrganizationManagementLevel: can_manage_organization",
response.json["message"],
)

def test_permissions_foreign_committee_cml_error(self) -> None:
self.set_models(self.test_models)
self.set_models(
{
Expand All @@ -1340,10 +1361,29 @@ def test_permissions_missing_payload_committee_permission(self) -> None:
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 2})
self.assert_status_code(response, 403)
self.assertIn(
"Missing permission: CommitteeManagementLevel can_manage in committee 2",
"You are not allowed to perform action meeting.clone. Missing OrganizationManagementLevel: can_manage_organization",
response.json["message"],
)

def test_permissions_oml_can_manage(self) -> None:
self.set_models(self.test_models)
self.set_models(
{
"committee/2": {"organization_id": 1},
"user/1": {
"organization_management_level": "can_manage_organization",
},
}
)
response = self.request("meeting.clone", {"meeting_id": 1, "committee_id": 2})
self.assert_status_code(response, 200)
self.assert_model_exists(
"meeting/1", {"is_active_in_organization_id": 1, "committee_id": 1}
)
self.assert_model_exists(
"meeting/2", {"is_active_in_organization_id": 1, "committee_id": 2}
)

def test_permissions_missing_source_committee_permission(self) -> None:
self.set_models(self.test_models)
self.set_models(
Expand Down

0 comments on commit bf23eb5

Please sign in to comment.