Skip to content

Commit

Permalink
Add permissions to motion actions and update tests. (#643)
Browse files Browse the repository at this point in the history
* Add permissions to motion.sort.

* Add permissions to motion.follow_recommendation.

* Add permissions to motion.set_recommendation.

* Add permissions to motion.reset_recommendation.

* Fix Layout.

* Add permissions to motion.set_support_self.

* Add permission to motion.reset_state.

* Add permissions to motion.set_state.

* Add permissions to motion.delete. Add PermissionHelperMixin and use it.

* Add permission to motion create. Update tests.

* Layout fix

* Fix typo

* Add permissions to motion.update and update tests.

* Modify mixin, add more code into the PermissionMixin and change the name of the method. Update tests.

* Move check for can_create into an else block in motion.create.

* Update create/update permission denied msg. Use forbidden fields.

* Allow motion/submitter_ids not set in permission checks.

* Move one guardian condition up in the code. Refactor submitters test.

* Update test, specialize test checks.

* Update motion.update and tests.

* Refactor motion.update.
  • Loading branch information
reiterl committed Apr 26, 2021
1 parent 54be90b commit 4470bb8
Show file tree
Hide file tree
Showing 21 changed files with 817 additions and 31 deletions.
48 changes: 46 additions & 2 deletions openslides_backend/action/actions/motion/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from typing import Any, Dict

from ....models.models import Motion
from ....shared.exceptions import ActionException
from ....permissions.permission_helper import has_perm
from ....permissions.permissions import Permissions
from ....shared.exceptions import ActionException, PermissionDenied
from ....shared.patterns import POSITIVE_NUMBER_REGEX, Collection, FullQualifiedId
from ....shared.schema import id_list_schema, optional_id_schema
from ...mixins.create_action_with_dependencies import CreateActionWithDependencies
Expand Down Expand Up @@ -37,7 +39,6 @@ class MotionCreate(
model = Motion()
schema = DefaultSchema(Motion()).get_create_schema(
optional_properties=[
"meeting_id",
"title",
"number",
"state_extension",
Expand Down Expand Up @@ -178,3 +179,46 @@ def update_instance(self, instance: Dict[str, Any]) -> Dict[str, Any]:
)

return instance

def check_permissions(self, instance: Dict[str, Any]) -> None:
# Check can create amendment if needed else check can_create
if instance.get("lead_motion_id"):
perm = Permissions.Motion.CAN_CREATE_AMENDMENTS
if not has_perm(self.datastore, self.user_id, perm, instance["meeting_id"]):
msg = f"You are not allowed to perform action {self.name}."
msg += f" Missing permission: {perm}"
raise PermissionDenied(msg)

else:
perm = Permissions.Motion.CAN_CREATE
if not has_perm(self.datastore, self.user_id, perm, instance["meeting_id"]):
msg = f"You are not allowed to perform action {self.name}."
msg += f" Missing permission: {perm}"
raise PermissionDenied(msg)

# if not can manage whitelist the fields.
perm = Permissions.Motion.CAN_MANAGE
if not has_perm(self.datastore, self.user_id, perm, instance["meeting_id"]):
whitelist = [
"title",
"text",
"reason",
"lead_motion_id",
"amendment_paragraph_$",
"category_id",
"statute_paragraph_id",
"workflow_id",
"id",
"meeting_id",
]
if instance.get("lead_motion_id"):
whitelist.append("motion_block_id")
forbidden_fields = []
for field in instance:
if field not in whitelist:
forbidden_fields.append(field)

if forbidden_fields:
msg = f"You are not allowed to perform action {self.name}."
msg += f" Forbidden fields: {', '.join(forbidden_fields)}"
raise PermissionDenied(msg)
36 changes: 35 additions & 1 deletion openslides_backend/action/actions/motion/delete.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
from typing import Any, Dict

from ....models.models import Motion
from ....permissions.permission_helper import has_perm
from ....permissions.permissions import Permissions
from ....shared.exceptions import PermissionDenied
from ....shared.patterns import Collection, FullQualifiedId
from ...generics.delete import DeleteAction
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from .mixins import PermissionHelperMixin


@register_action("motion.delete")
class MotionDelete(DeleteAction):
class MotionDelete(DeleteAction, PermissionHelperMixin):
"""
Action to delete motions.
"""

model = Motion()
schema = DefaultSchema(Motion()).get_delete_schema()

def check_permissions(self, instance: Dict[str, Any]) -> None:
motion = self.datastore.get(
FullQualifiedId(Collection("motion"), instance["id"]),
[
"state_id",
"submitter_ids",
"meeting_id",
],
)
if has_perm(
self.datastore,
self.user_id,
Permissions.Motion.CAN_MANAGE,
motion["meeting_id"],
):
return

if self.is_allowed_and_submitter(
motion.get("submitter_ids", []),
motion["state_id"],
):
return

msg = f"You are not allowed to perform action {self.name}."
msg += f"Missing permission: {Permissions.Motion.CAN_MANAGE}"
raise PermissionDenied(msg)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict

from ....models.models import Motion
from ....permissions.permissions import Permissions
from ....services.datastore.commands import GetManyRequest
from ....shared.patterns import Collection, FullQualifiedId
from ...util.default_schema import DefaultSchema
Expand All @@ -15,6 +16,7 @@ class MotionFollowRecommendationAction(MotionSetStateAction):

model = Motion()
schema = DefaultSchema(Motion()).get_update_schema()
permission = Permissions.Motion.CAN_MANAGE

def get_updated_instances(self, action_data: ActionData) -> ActionData:
ids = [instance["id"] for instance in action_data]
Expand Down
23 changes: 23 additions & 0 deletions openslides_backend/action/actions/motion/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import List

from ....services.datastore.commands import GetManyRequest
from ....shared.patterns import Collection, FullQualifiedId
from ...action import Action


class PermissionHelperMixin(Action):
def is_allowed_and_submitter(self, submitter_ids: List[int], state_id: int) -> bool:
if not submitter_ids:
return False
state = self.datastore.get(
FullQualifiedId(Collection("motion_state"), state_id),
["allow_submitter_edit"],
)
if not state.get("allow_submitter_edit"):
return False
get_many_request = GetManyRequest(
Collection("motion_submitter"), submitter_ids, ["user_id"]
)
result = self.datastore.get_many([get_many_request])
submitters = result.get(Collection("motion_submitter"), {}).values()
return any(self.user_id == s.get("user_id") for s in submitters)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict

from ....models.models import Motion
from ....permissions.permissions import Permissions
from ...generics.update import UpdateAction
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
Expand All @@ -15,6 +16,7 @@ class MotionResetRecommendationAction(UpdateAction):

model = Motion()
schema = DefaultSchema(Motion()).get_update_schema()
permission = Permissions.Motion.CAN_MANAGE

def update_instance(self, instance: Dict[str, Any]) -> Dict[str, Any]:
"""
Expand Down
2 changes: 2 additions & 0 deletions openslides_backend/action/actions/motion/reset_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict

from ....models.models import Motion
from ....permissions.permissions import Permissions
from ....shared.exceptions import ActionException
from ....shared.patterns import Collection, FullQualifiedId
from ...generics.update import UpdateAction
Expand All @@ -18,6 +19,7 @@ class MotionResetStateAction(UpdateAction, SetNumberMixin):

model = Motion()
schema = DefaultSchema(Motion()).get_update_schema()
permission = Permissions.Motion.CAN_MANAGE_METADATA

def update_instance(self, instance: Dict[str, Any]) -> Dict[str, Any]:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict

from ....models.models import Motion
from ....permissions.permissions import Permissions
from ....shared.exceptions import ActionException
from ....shared.patterns import Collection, FullQualifiedId
from ...generics.update import UpdateAction
Expand All @@ -17,6 +18,7 @@ class MotionSetRecommendationAction(UpdateAction):

model = Motion()
schema = DefaultSchema(Motion()).get_update_schema(["recommendation_id"])
permission = Permissions.Motion.CAN_MANAGE

def update_instance(self, instance: Dict[str, Any]) -> Dict[str, Any]:
"""
Expand Down
33 changes: 31 additions & 2 deletions openslides_backend/action/actions/motion/set_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
from typing import Any, Dict

from ....models.models import Motion
from ....shared.exceptions import ActionException
from ....permissions.permission_helper import has_perm
from ....permissions.permissions import Permissions
from ....shared.exceptions import ActionException, PermissionDenied
from ....shared.patterns import Collection, FullQualifiedId
from ...generics.update import UpdateAction
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from .mixins import PermissionHelperMixin
from .set_number_mixin import SetNumberMixin


@register_action("motion.set_state")
class MotionSetStateAction(UpdateAction, SetNumberMixin):
class MotionSetStateAction(UpdateAction, SetNumberMixin, PermissionHelperMixin):
"""
Set the state in a motion.
"""
Expand Down Expand Up @@ -61,3 +64,29 @@ def update_instance(self, instance: Dict[str, Any]) -> Dict[str, Any]:
)
instance["last_modified"] = round(time.time())
return instance

def check_permissions(self, instance: Dict[str, Any]) -> None:
motion = self.datastore.get(
FullQualifiedId(Collection("motion"), instance["id"]),
[
"state_id",
"submitter_ids",
"meeting_id",
],
)
if has_perm(
self.datastore,
self.user_id,
Permissions.Motion.CAN_MANAGE_METADATA,
motion["meeting_id"],
):
return

if self.is_allowed_and_submitter(
motion.get("submitter_ids", []), motion["state_id"]
):
return

msg = "You are not allowed to perform action {self.name}."
msg += f"Missing permission: {Permissions.Motion.CAN_MANAGE_METADATA}"
raise PermissionDenied(msg)
3 changes: 3 additions & 0 deletions openslides_backend/action/actions/motion/set_support_self.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ....models.models import Motion
from ....permissions.permissions import Permissions
from ....services.datastore.commands import GetManyRequest
from ....shared.exceptions import ActionException
from ....shared.patterns import Collection
Expand All @@ -24,6 +25,8 @@ class MotionSetSupportSelfAction(UpdateAction):
"support": {"type": "boolean"},
},
)
permission_id = "motion_id"
permission = Permissions.Motion.CAN_SUPPORT

def get_updated_instances(self, action_data: ActionData) -> ActionData:
motion_get_many_request = GetManyRequest(
Expand Down
2 changes: 2 additions & 0 deletions openslides_backend/action/actions/motion/sort.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ....models.models import Motion
from ....permissions.permissions import Permissions
from ...generics.update import UpdateAction
from ...mixins.singular_action_mixin import SingularActionMixin
from ...mixins.tree_sort_mixin import TreeSortMixin
Expand All @@ -15,6 +16,7 @@ class MotionSort(TreeSortMixin, SingularActionMixin, UpdateAction):

model = Motion()
schema = DefaultSchema(Motion()).get_tree_sort_schema()
permission = Permissions.Motion.CAN_MANAGE

def get_updated_instances(self, action_data: ActionData) -> ActionData:
action_data = super().get_updated_instances(action_data)
Expand Down
47 changes: 45 additions & 2 deletions openslides_backend/action/actions/motion/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from typing import Any, Dict

from ....models.models import Motion
from ....shared.exceptions import ActionException
from ....permissions.permission_helper import has_perm
from ....permissions.permissions import Permissions
from ....shared.exceptions import ActionException, PermissionDenied
from ....shared.filters import FilterOperator
from ....shared.patterns import (
KEYSEPARATOR,
Expand All @@ -15,12 +17,13 @@
from ...generics.update import UpdateAction
from ...util.default_schema import DefaultSchema
from ...util.register import register_action
from .mixins import PermissionHelperMixin

RECOMMENDATION_EXTENSION_REFERENCE_IDS_PATTERN = re.compile(r"\[(?P<fqid>\w+/\d+)\]")


@register_action("motion.update")
class MotionUpdate(UpdateAction):
class MotionUpdate(UpdateAction, PermissionHelperMixin):
"""
Action to update motions.
"""
Expand Down Expand Up @@ -121,3 +124,43 @@ def set_recommendation_extension_reference_ids(
instance[
"recommendation_extension_reference_ids"
] = recommendation_extension_reference_ids

def check_permissions(self, instance: Dict[str, Any]) -> None:
motion = self.datastore.get(
FullQualifiedId(self.model.collection, instance["id"]),
["meeting_id", "state_id", "submitter_ids"],
)

# check for can_manage, all allowed
perm = Permissions.Motion.CAN_MANAGE
if has_perm(self.datastore, self.user_id, perm, motion["meeting_id"]):
return

# check for can_manage_metadata and whitelist
perm = Permissions.Motion.CAN_MANAGE_METADATA
allowed_fields = ["id"]
if has_perm(self.datastore, self.user_id, perm, motion["meeting_id"]):
allowed_fields += [
"category_id",
"motion_block_id",
"origin",
"supporters_id",
"recommendation_extension",
]

# check for self submitter and whitelist
if self.is_allowed_and_submitter(
motion.get("submitter_ids", []), motion["state_id"]
):
allowed_fields += [
"title",
"text",
"reason",
"amendment_paragraph_$",
]

forbidden_fields = [field for field in instance if field not in allowed_fields]
if forbidden_fields:
msg = f"You are not allowed to perform action {self.name}."
msg += f"Forbidden fields: {', '.join(forbidden_fields)}"
raise PermissionDenied(msg)
Loading

0 comments on commit 4470bb8

Please sign in to comment.