Skip to content

Commit

Permalink
Implement anonymous group (#2551)
Browse files Browse the repository at this point in the history
* Implement anonymous group

* Stop anonymous group from being set

* Make participant import actions act as if the anonymous group doesn't exist

* Test permission helper change

* Update wiki

* Whitelist permissions for and forbid changing the name of anonymous group

* Forbid giving anonymous groups model-wise write perms

* cleanup

* Make changes
  • Loading branch information
luisa-beerboom authored Aug 13, 2024
1 parent aa5bcf9 commit a21c7a4
Show file tree
Hide file tree
Showing 52 changed files with 1,000 additions and 75 deletions.
2 changes: 2 additions & 0 deletions docs/actions/chat_group.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
Creates a new chat group in the given meeting. Only enabled, if `organization/enable_chat` **and** `meeting/enable_chat` is true. The `weight` must be set to `max(weight)+1` of all chat groups of the meeting.
The name of a chat group is unique.

The `write_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs `chat.can_manage`.
2 changes: 2 additions & 0 deletions docs/actions/chat_group.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
## Action
Updates the chat group. Only enabled, if `organization/enable_chat` **and** `meeting/enable_chat` is true. The name of a chat group is unique.

The `write_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs `chat.can_manage`.
2 changes: 1 addition & 1 deletion docs/actions/group.delete.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
```

## Action
Deletes the group. If the group has users or `default_group_for_meeting_id` or `admin_group_for_meeting_id` set, the deletion is not allowed.
Deletes the group. If the group has users or `default_group_for_meeting_id`, `anonymous_group_for_meeting_id` or `admin_group_for_meeting_id` set, the deletion is not allowed.

## Permissions
The user needs `user.can_manage`.
17 changes: 17 additions & 0 deletions docs/actions/group.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,22 @@
## Action
Updates the group. Permissions are restricted to the following enum: https://github.com/OpenSlides/openslides-backend/blob/fae36a0b055bbaa463da4768343080c285fe8178/global/meta/models.yml#L1621-L1656

If the group is the meetings anonymous group, the name may not be changed and the permissions have to be in the following whitelist:
- agenda_item.can_see,
- agenda_item.can_see_internal,
- agenda_item.can_see_moderator_notes,
- assignment.can_see,
- list_of_speakers.can_see,
- mediafile.can_see,
- meeting.can_see_autopilot,
- meeting.can_see_frontpage,
- meeting.can_see_history,
- meeting.can_see_livestream,
- motion.can_see,
- motion.can_see_internal,
- projector.can_see,
- user.can_see,
- user.can_see_sensitive_data

## Permissions
The user needs `user.can_manage` to change `name` and `permission`, for `external_id` meeting admin rights are mandatory.
6 changes: 5 additions & 1 deletion docs/actions/meeting.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,11 @@ 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.
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. Same for `enable_anonymous` and `locked_from_inside` being true at the same time

If `enable_anonymous` is set, this action will create an anonymous group for the meeting. This will have the name `Anonymous` and otherwise differ from the other groups in the meeting due to having `anonymous_group_for_meeting_id` set.

The meetings `anonymous_group_id` may not be used for the `assignment_poll_default_group_ids`, `topic_poll_default_group_ids` and `motion_poll_default_group_ids` fields.

## Permissions
- Users with `meeting.can_manage_settings` can modify group A
Expand Down
2 changes: 2 additions & 0 deletions docs/actions/meeting_user.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
The action creates a meeting_user item. `vote_delegated_to_id` and `vote_delegations_from_ids` have special checks, see user checks.
If `locked_out` is set, it checks against the present `user.can_manage` and all admin statuses and throws an error if any are present.

Will throw an error if the `group_ids` contain the meetings `anonymous_group_id`.

## Permissions
Group A: The request user needs `user.can_manage`.

Expand Down
5 changes: 4 additions & 1 deletion docs/actions/meeting_user.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@
```
## Internal action
Updates a meeting_user. `vote_delegated_to_id` and `vote_delegations_from_ids` has special checks, see user checks.
The action checks, whether at the end the field `locked_out` will be set together with any of `user.can_manage` or any admin statuses on the updated meeting_user and throws an error if that is the case.

Will throw an error if the `group_ids` contain the meetings `anonymous_group_id`.

The action checks, whether at the end the field `locked_out` will be set together with any of `user.can_manage` or any admin statuses on the updated meeting_user and throws an error if that is the case.
2 changes: 2 additions & 0 deletions docs/actions/motion_comment_section.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
## Action
Creates a new comment section. The `weight` must be set to `max+1` of all comment sections of the meeting. The given groups must belong to the same meeting.

The `write_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs `motion.can_manage`.
2 changes: 2 additions & 0 deletions docs/actions/motion_comment_section.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
## Action
Updates the comment section. The given groups must belong to the same meeting.

The `write_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs `motion.can_manage`.
2 changes: 1 addition & 1 deletion docs/actions/participant.json_upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Same as in [account.json_upload#user-matching](account.json_upload.md#user-match
This action is the first part of the actions for the import of participants (mean: users in a meeting).
It should use the `JsonUploadMixin` and is a single payload action.

The `groups` field includes a list of group names. The group names will be looked up in the meeting.
The `groups` field includes a list of group names. The group names will be looked up among the groups in the meeting, with the exception of the meetings anonymous group, which will be ignored.
If a group is found, info will be *done* and id is the id of the group. If no group is found, info will be *warning*.
If no group in groups is found at all, the entry state will be *error* and import shouldn't be possible.

Expand Down
2 changes: 2 additions & 0 deletions docs/actions/poll.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ If the `type` is `pseudoanonymous`, `is_pseudoanonymized` has to be set to `true

If the `content_object_id` points to a `motion` and the `motion_state` of the motion misses `allow_create_poll`, it is forbidden to create a poll.

The `entitled_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs:
- `motion.can_manage_polls` if the poll's content object is a motion
Expand Down
2 changes: 2 additions & 0 deletions docs/actions/poll.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ For analog polls: If the state is created and at least one vote value is given (

For electronic polls some fields can only be updated, if the state is *created*.

The `entitled_group_ids` may not contain the meetings `anonymous_group_id`.

## Permissions
The request user needs:
- `motion.can_manage_polls` if the poll's content object is a motion
Expand Down
1 change: 1 addition & 0 deletions docs/actions/user.create.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Creates a user.
* The given `gender` must be present in `organization/genders`
* If `saml_id` is set in payload, there may be no `password` or `default_password` set or generated and `set_change_own_password` will be set to False.
* The `member_number` must be unique within all users.
* Will throw an error if the `group_ids` contain the meetings `anonymous_group_id`.
* The action checks, whether at the end the field `locked_out` will be set together with any of `user.can_manage` or any admin statuses on the created user and throws an error if that is the case.

### Generate a username
Expand Down
1 change: 1 addition & 0 deletions docs/actions/user.update.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Updates a user.
* Remove starting and trailing spaces from `username`, `first_name` and `last_name`
* The given `gender` must be present in `organization/genders`
* The `member_number` must be unique within all users.
* Will throw an error if the `group_ids` contain the meetings `anonymous_group_id`.
* The action checks, whether at the end the field `locked_out` will be set together with any of `user.can_manage` or any admin statuses on the updated user and throws an error if that is the case.

Note: `is_present_in_meeting_ids` is not available in update, since there is no possibility to partially update this field. This can be done via [user.set_present](user.set_present.md).
Expand Down
2 changes: 1 addition & 1 deletion global/meta
Submodule meta updated 1 files
+9 −0 models.yml
8 changes: 7 additions & 1 deletion openslides_backend/action/actions/chat_group/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ....models.models import ChatGroup
from ....permissions.permissions import Permissions
from ...generics.create import CreateAction
from ...mixins.forbid_anonymous_group_mixin import ForbidAnonymousGroupMixin
from ...mixins.weight_mixin import WeightMixin
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
Expand All @@ -11,7 +12,11 @@

@register_action("chat_group.create")
class ChatGroupCreate(
WeightMixin, ChatEnabledMixin, CheckUniqueNameMixin, CreateAction
WeightMixin,
ChatEnabledMixin,
CheckUniqueNameMixin,
CreateAction,
ForbidAnonymousGroupMixin,
):
"""
Action to create a chat group.
Expand All @@ -28,4 +33,5 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
instance = super().update_instance(instance)
self.check_name_unique(instance)
instance["weight"] = self.get_weight(instance["meeting_id"])
self.check_anonymous_not_in_list_fields(instance, ["write_group_ids"])
return instance
6 changes: 5 additions & 1 deletion openslides_backend/action/actions/chat_group/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
from ....permissions.permissions import Permissions
from ....shared.patterns import fqid_from_collection_and_id
from ...generics.update import UpdateAction
from ...mixins.forbid_anonymous_group_mixin import ForbidAnonymousGroupMixin
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from .mixins import ChatEnabledMixin, CheckUniqueNameMixin


@register_action("chat_group.update")
class ChatGroupUpdate(ChatEnabledMixin, CheckUniqueNameMixin, UpdateAction):
class ChatGroupUpdate(
ChatEnabledMixin, CheckUniqueNameMixin, UpdateAction, ForbidAnonymousGroupMixin
):
"""
Action to update a chat group.
"""
Expand All @@ -31,4 +34,5 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
)
if instance["name"] != chat_group.get("name"):
self.check_name_unique(instance)
self.check_anonymous_not_in_list_fields(instance, ["write_group_ids"])
return instance
17 changes: 15 additions & 2 deletions openslides_backend/action/actions/group/update.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from typing import Any

from openslides_backend.shared.exceptions import PermissionDenied
from openslides_backend.shared.exceptions import ActionException, PermissionDenied

from ....models.models import Group
from ....permissions.permission_helper import filter_surplus_permissions, is_admin
from ....permissions.permission_helper import (
check_if_perms_are_allowed_for_anonymous,
filter_surplus_permissions,
is_admin,
)
from ....permissions.permissions import Permissions
from ....shared.patterns import fqid_from_collection_and_id
from ...generics.update import UpdateAction
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
Expand All @@ -28,6 +33,14 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
instance["permissions"] = filter_surplus_permissions(
instance["permissions"]
)
if self.datastore.get(
fqid_from_collection_and_id("group", instance["id"]),
["anonymous_group_for_meeting_id"],
).get("anonymous_group_for_meeting_id"):
if perms := instance.get("permissions", []):
check_if_perms_are_allowed_for_anonymous(perms)
if "name" in instance:
raise ActionException("Cannot change name of anonymous group.")
return instance

def check_permissions(self, instance: dict[str, Any]) -> None:
Expand Down
82 changes: 63 additions & 19 deletions openslides_backend/action/actions/meeting/update.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, cast

from openslides_backend.action.mixins.check_unique_name_mixin import (
CheckUniqueInContextMixin,
Expand All @@ -20,10 +20,12 @@
from ....shared.patterns import fqid_from_collection_and_id
from ....shared.util import ONE_ORGANIZATION_FQID
from ...generics.update import UpdateAction
from ...mixins.forbid_anonymous_group_mixin import ForbidAnonymousGroupMixin
from ...mixins.send_email_mixin import EmailCheckMixin, EmailSenderCheckMixin
from ...util.assert_belongs_to_meeting import assert_belongs_to_meeting
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from ..group.create import GroupCreate
from .mixins import GetMeetingIdFromIdMixin, MeetingCheckTimesMixin

meeting_settings_keys = [
Expand Down Expand Up @@ -173,6 +175,7 @@ class MeetingUpdate(
UpdateAction,
GetMeetingIdFromIdMixin,
MeetingCheckTimesMixin,
ForbidAnonymousGroupMixin,
):
model = Meeting()
schema = DefaultSchema(Meeting()).get_update_schema(
Expand Down Expand Up @@ -210,24 +213,7 @@ 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."
)
self.check_locking(instance, set_as_template)
organization = self.datastore.get(
ONE_ORGANIZATION_FQID, ["require_duplicate_from"], lock_result=False
)
Expand Down Expand Up @@ -283,6 +269,35 @@ def update_instance(self, instance: dict[str, Any]) -> dict[str, Any]:
raise ActionException("It is not allowed to end jitsi_domain with '/'.")

self.check_start_and_end_time(instance)

anonymous_group_id = self.datastore.get(
fqid_from_collection_and_id("meeting", instance["id"]),
["anonymous_group_id"],
).get("anonymous_group_id")

if instance.get("enable_anonymous") and not anonymous_group_id:
group_result = self.execute_other_action(
GroupCreate,
[
{
"name": "Anonymous",
"meeting_id": instance["id"],
}
],
)
instance["anonymous_group_id"] = anonymous_group_id = cast(
list[dict[str, Any]], group_result
)[0]["id"]
self.check_anonymous_not_in_list_fields(
instance,
[
"assignment_poll_default_group_ids",
"topic_poll_default_group_ids",
"motion_poll_default_group_ids",
],
anonymous_group_id,
)

instance = super().update_instance(instance)
return instance

Expand Down Expand Up @@ -366,3 +381,32 @@ def get_committee_id(self, meeting_id: int) -> int:
lock_result=False,
)["committee_id"]
return self._committee_id

def check_locking(self, instance: dict[str, Any], set_as_template: bool) -> None:
db_meeting = self.datastore.get(
fqid_from_collection_and_id("meeting", instance["id"]),
["template_for_organization_id", "locked_from_inside", "enable_anonymous"],
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:
if (
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 (
instance.get("enable_anonymous")
if instance.get("enable_anonymous") is not None
else db_meeting.get("enable_anonymous")
):
raise ActionException(
"A meeting cannot be locked from the inside and have anonymous enabled at the same time."
)
11 changes: 9 additions & 2 deletions openslides_backend/action/actions/meeting_user/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from .history_mixin import MeetingUserHistoryMixin
from .mixin import CheckLockOutPermissionMixin, meeting_user_standard_fields
from .mixin import (
CheckLockOutPermissionMixin,
MeetingUserGroupMixin,
meeting_user_standard_fields,
)


@register_action("meeting_user.create", action_type=ActionType.BACKEND_INTERNAL)
class MeetingUserCreate(
MeetingUserHistoryMixin, CreateAction, CheckLockOutPermissionMixin
MeetingUserHistoryMixin,
CreateAction,
MeetingUserGroupMixin,
CheckLockOutPermissionMixin,
):
"""
Action to create a meeting user.
Expand Down
Loading

0 comments on commit a21c7a4

Please sign in to comment.