Skip to content

Commit

Permalink
Add setting for closed meeting (#2523)
Browse files Browse the repository at this point in the history
* Add setting for closed meeting

* Edit meeting.update and permission system, test almost everything

* Forbid locking template meetings

* Add all but the user.create tests

* Add tests to user.create

* Edit send_email permission handling

* Fix send_invitation_email permission tests

* Amend user.set_present

* Amend user.toggle_presence_by_number

* Amend user.create and user.update

* Amend create update perm mixin for users

* Forbid cloning of locked meetings

* Ensure archiving still works

* Amend search_for_id_by_external_id presenter

* Fix test

* Update datastore calls and wiki entries
  • Loading branch information
luisa-beerboom authored Jul 18, 2024
1 parent 596d026 commit a55f527
Show file tree
Hide file tree
Showing 168 changed files with 2,256 additions and 154 deletions.
2 changes: 2 additions & 0 deletions docs/actions/meeting.clone.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ 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)

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

### Pre Updating fields

The fields `welcome_title, description, start_time, end_time, location, organization_tag_ids, name` could be updated for the
Expand Down
1 change: 1 addition & 0 deletions docs/actions/meeting.import.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ The user must be the committee manager of the given committee.
### Info

The `meeting` object must contain a valid `_migration_index` on root level.
The `meeting` object cannot have `locked_from_inside` set to true.
4 changes: 3 additions & 1 deletion docs/actions/meeting.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
location: string;
start_time: timestamp;
end_time: timestamp;
locked_from_inside: boolean;

conference_show: boolean;
conference_auto_connect: boolean;
Expand Down Expand Up @@ -183,7 +184,6 @@
jitsi_domain: string;
jitsi_room_name: string;
jitsi_room_password: string;
enable_chat: boolean;
}
```

Expand All @@ -193,6 +193,8 @@ Updates the meeting.
If `set_as_template` is `True`, `template_for_organization_id` has to be set to `1`. If it is `False`, it has to be set to `None`.
`reference_projector_id` can only be set to a projector, which is not internal.

This action doesn't allow for a meeting to be set as a template and have `locked_from_inside` set to true at the same time. if this would be the result of an action call, an exception will be thrown.

## Permissions
- Users with `meeting.can_manage_settings` can modify group A
- Users with `user.can_update` can modify group B
Expand Down
2 changes: 1 addition & 1 deletion docs/actions/participant.json_upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ Permissions are analogue to `user.create` and `user.update`. The `saml_id` can b

In case of an update, remove fields from the payload that don't change the content compared to database to avoid unnecessary
permission errors. Don't forget the special permissions for `default_password` on `user.update`.
Anyway the user must have the permission `user.can_manage`, `cml.can_manage` or >= `oml.can_manage_users`. Otherwise the whole import will be finished with an exception.
Anyway the user must have the permission `user.can_manage` or, if the meeting is not locked via the setting `locked_from_inside`, `cml.can_manage` or >= `oml.can_manage_users`. Otherwise the whole import will be finished with an exception.
2 changes: 2 additions & 0 deletions docs/actions/user.assign_meetings.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Go through all meetings:
If it doesn't find the `group_name` in at least one meeting, throw an `ActionException`.
Returns dictionary with `"succeeded": [meeting_ids], "standard_group": [meeting_ids], "nothing": [meeting_ids]`.

Will raise an error if some of the selected meetings are locked from the inside via the `locked_from_inside` setting.

## Permissions
The request user needs OML `can_manage_users`

Expand Down
8 changes: 4 additions & 4 deletions docs/actions/user.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ The request user needs `user.can_manage` in the meeting of meeting_id.

Group C:

The request user must satisfy at least one of:
- the OML `can_manage_users`
- For each meeting:
* `user.can_manage` for the meeting, OR
For each meeting the request user must satisfy at least one of:
- `user.can_manage` for the meeting, OR
- If the meeting is not locked via `locked_from_inside` setting:
* the OML `can_manage_users` in the organization
* The CML `can_manage` for the committee of the meeting

Group D:
Expand Down
3 changes: 1 addition & 2 deletions docs/actions/user.send_invitation_email.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,4 @@ It does an equal string formatting as for the subject. Use `meeting/users_email
Sending email is no longer refused, the wrong keyword will be injected instead.

## Permissions
The requesting user needs either the permission `user.can_update` (if a `meeting_id` is given) in
each referenced meeting or the permission OML `can_manage_users`.
The requesting user needs the permission `user.can_update` if a `meeting_id` is given or, if not, the permission OML `can_manage_users`.
5 changes: 3 additions & 2 deletions docs/actions/user.set_present.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Sets the user's present status in the given meeting. If `present` is true, `meet
## Permissions

One of the following has to be true:
* The request user has the OML `can_manage_users`
* The request user has the CML `can_manage` in the given meeting's committee
* The meeting is not locked via the setting `locked_from_inside` and:
* The request user has the OML `can_manage_users`
* The request user has the CML `can_manage` in the given meeting's committee
* The request user has `user.can_update` or `user.can_manage_presence` in the given meeting
* The `user_id` is equal to the request user id and the setting `users_allow_self_set_present` is set to `True` in the given meeting
5 changes: 3 additions & 2 deletions docs/actions/user.toggle_presence_by_number.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ If the action is successful, it returns the id of the modified user. Thus the ac
## Permission

One of the following has to be true:
* The request user has the OML `can_manage_users`
* The request user has the CML `can_manage` in the given meeting's committee
* The meeting is not locked via the setting `locked_from_inside` and:
* The request user has the OML `can_manage_users`
* The request user has the CML `can_manage` in the given meeting's committee
* The request user has `user.can_update` in the given meeting
9 changes: 5 additions & 4 deletions docs/actions/user.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ The request user needs `user.can_update` in each referenced meeting.

Group C:

The request user must satisfy at least one of:
- the OML `can_manage_users`
- `user.can_update` for the meeting, OR
- The CML `can_manage` for the committee of the meeting
For each meeting the request user must satisfy at least one of:
- `user.can_manage` for the meeting, OR
- If the meeting is not locked via `locked_from_inside` setting:
* the OML `can_manage_users` in the organization
* The CML `can_manage` for the committee of the meeting

Group D:

Expand Down
1 change: 1 addition & 0 deletions docs/presenters/export_meeting.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ JSON with export

# Logic
The presenter exports the meeting, the collections which belong to the meeting and users of the meeting. It uses the meeting.user_id for that. And it excludes the organization tags and the committee.
Will raise an exception if the meeting is locked voa the `locked_from_inside` setting.

# Permissions
The request user must have the `SUPERADMIN` organization management level.
Expand Down
4 changes: 3 additions & 1 deletion docs/presenters/get_user_related_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
id: Id;
name: String;
is_active_in_organization_id: Id;
is_locked: boolean;
motion_submitter_ids: Id[];
assignemnt_candidate_ids: Id[];
assignment_candidate_ids: Id[];
speaker_ids: Id[];
}]
}
Expand All @@ -29,6 +30,7 @@

It iterates over the given `user_ids`. For every id of `user_ids` all objects are searched which are associated with that id. This means that for every committee it is checked if the user (specified by the id) is a manager or member of the committee, and for every meeting if the user is listed as a speaker of any `agenda_item` or as a submitter of any `motion` or as a candidate of any `assignment`.
The result is a dictionary whose keys are the `user_ids`. The values are threefolded: `organization_management_level` contains the OML of the user. The two other values are arrays, one for the `committees` and one for the `meetings`. If a user is no member of any committee, then the `committees` array is empty and omitted. The same applies to the `meetings` array.
If a meeting has `locked_from_inside` set to true, `is_locked` will be true and `motion_submitter_ids`, `assignment_candidate_ids` and `speaker_ids` will be left out for this meeting, unless the calling user is in the meeting himself.

Every committee is given by its name and id as well as the CML of the user (given by the `user_id`). Every meeting is given by its name, its id and its `is_active_in_organization_id` (to indicate if the meeting is archived).

Expand Down
2 changes: 2 additions & 0 deletions docs/presenters/search_for_id_by_external_id.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ It should be search for an `external_id` in the given collection (`group`, `meet

Following error cases could be encountered: "No item with 'external_id' was found" and "More then one item with 'external_id' were found".

If the `group` collection is given, and there are locked meetings, the presenter will act as if these groups do not exist. They will not count towards the result.

For searching a user by `saml_id` see [search_users](search_users.md).

## Permissions
Expand Down
2 changes: 1 addition & 1 deletion global/meta
Submodule meta updated 1 files
+3 −0 models.yml
3 changes: 3 additions & 0 deletions openslides_backend/action/actions/meeting/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
self.check_one_meeting(instance)
meeting = self.get_meeting_from_json(meeting_json)

if meeting.get("locked_from_inside"):
raise ActionException("Cannot clone locked meeting.")

if committee_id := instance.get("committee_id"):
meeting["committee_id"] = committee_id

Expand Down
5 changes: 5 additions & 0 deletions openslides_backend/action/actions/meeting/import_.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def prefetch(self, action_data: ActionData) -> None:

def preprocess_data(self, instance: dict[str, Any]) -> dict[str, Any]:
self.check_one_meeting(instance)
self.check_locked(instance)
self.remove_not_allowed_fields(instance)
self.set_committee_and_orga_relation(instance)
instance = self.migrate_data(instance)
Expand All @@ -155,6 +156,10 @@ def check_one_meeting(self, instance: dict[str, Any]) -> None:
if len(instance["meeting"]["meeting"]) != 1:
raise ActionException("Need exactly one meeting in meeting collection.")

def check_locked(self, instance: dict[str, Any]) -> None:
if list(instance["meeting"]["meeting"].values())[0].get("locked_from_inside"):
raise ActionException("Cannot import a locked meeting.")

def remove_not_allowed_fields(self, instance: dict[str, Any]) -> None:
json_data = instance["meeting"]

Expand Down
19 changes: 19 additions & 0 deletions openslides_backend/action/actions/meeting/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"location",
"start_time",
"end_time",
"locked_from_inside",
"conference_show",
"conference_auto_connect",
"conference_los_restriction",
Expand Down Expand Up @@ -208,6 +209,24 @@ def validate_instance(self, instance: dict[str, Any]) -> None:
def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
# handle set_as_template
set_as_template = instance.pop("set_as_template", None)
db_meeting = self.datastore.get(
fqid_from_collection_and_id("meeting", instance["id"]),
["template_for_organization_id", "locked_from_inside"],
lock_result=False,
)
lock_meeting = (
instance.get("locked_from_inside")
if instance.get("locked_from_inside") is not None
else db_meeting.get("locked_from_inside")
)
if lock_meeting and (
set_as_template
if set_as_template is not None
else db_meeting.get("template_for_organization_id")
):
raise ActionException(
"A meeting cannot be locked from the inside and a template at the same time."
)
if set_as_template is True:
instance["template_for_organization_id"] = 1
elif set_as_template is False:
Expand Down
19 changes: 19 additions & 0 deletions openslides_backend/action/actions/user/assign_meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ....models.models import User
from ....permissions.management_levels import OrganizationManagementLevel
from ....services.datastore.commands import GetManyRequest
from ....shared.exceptions import ActionException
from ....shared.filters import And, FilterOperator
from ....shared.patterns import fqid_from_collection_and_id
Expand Down Expand Up @@ -34,6 +35,7 @@ class UserAssignMeetings(MeetingUserHelperMixin, UpdateAction):
use_meeting_ids_for_archived_meeting_check = True

def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
self.check_meetings(instance)
user_id = instance["id"]
meeting_ids = set(instance.pop("meeting_ids"))
group_name = instance.pop("group_name")
Expand Down Expand Up @@ -135,6 +137,23 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:

return instance

def check_meetings(self, instance: dict[str, Any]) -> None:
if meeting_ids := instance.get("meeting_ids"):
locked_meetings = [
str(id_)
for id_, meeting in self.datastore.get_many(
[GetManyRequest("meeting", meeting_ids, ["locked_from_inside"])],
lock_result=False,
)
.get("meeting", {})
.items()
if meeting.get("locked_from_inside")
]
if len(locked_meetings):
raise ActionException(
f"Cannot assign meetings because some selected meetings are locked: {', '.join(locked_meetings)}."
)

def create_action_result_element(
self, instance: dict[str, Any]
) -> ActionResultElement | None:
Expand Down
Loading

0 comments on commit a55f527

Please sign in to comment.