From c355326466148e7d40d0a48a1f13f31dc19637d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Fri, 1 Feb 2019 13:15:08 +0100 Subject: [PATCH] Added new flag to motion state to control access for different users. --- CHANGELOG.rst | 1 + openslides/motions/access_permissions.py | 19 ++++---- .../migrations/0021_state_access_level.py | 43 +++++++++++++++++++ openslides/motions/models.py | 28 +++++++++--- openslides/motions/serializers.py | 17 +++----- tests/integration/motions/test_viewset.py | 19 +++----- tests/unit/motions/test_projector.py | 10 ++--- 7 files changed, 95 insertions(+), 42 deletions(-) create mode 100644 openslides/motions/migrations/0021_state_access_level.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dbbc78568..755c39238 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -38,6 +38,7 @@ Motions: recommendations [#4037, #4132]. - Added timestampes for motions [#4134]. - New config option to set reason as required field [#4232] + - Added new flag to motion state to control access for different users [#4235]. User: - Added new admin group which grants all permissions. Users of existing group diff --git a/openslides/motions/access_permissions.py b/openslides/motions/access_permissions.py index 9c0e24e7d..7e4a20bc5 100644 --- a/openslides/motions/access_permissions.py +++ b/openslides/motions/access_permissions.py @@ -37,13 +37,17 @@ class MotionAccessPermissions(BaseAccessPermissions): is_submitter = False # Check see permission for this motion. - required_permission_to_see = full["state_required_permission_to_see"] - permission = ( - not required_permission_to_see - or await async_has_perm(user_id, required_permission_to_see) - or await async_has_perm(user_id, "motions.can_manage") - or is_submitter - ) + from .models import State + + if await async_has_perm(user_id, "motions.can_manage"): + level = State.MANAGERS_ONLY + elif await async_has_perm(user_id, "motions.can_manage_metadata"): + level = State.EXTENDED_MANAGERS + elif is_submitter: + level = State.EXTENDED_MANAGERS_AND_SUBMITTER + else: + level = State.ALL + permission = level >= full["state_access_level"] # Parse single motion. if permission: @@ -57,7 +61,6 @@ class MotionAccessPermissions(BaseAccessPermissions): data.append(full_copy) else: data = [] - return data diff --git a/openslides/motions/migrations/0021_state_access_level.py b/openslides/motions/migrations/0021_state_access_level.py new file mode 100644 index 000000000..c621faf2d --- /dev/null +++ b/openslides/motions/migrations/0021_state_access_level.py @@ -0,0 +1,43 @@ +# Generated by Django 2.1.5 on 2019-02-01 11:58 + +from django.db import migrations, models + + +def transform_required_permission_to_see_field(apps, schema_editor): + """ + Sets new access_level of states to EXTENDED_MANAGERS_AND_SUBMITTER + if required_permission_to_see is given + """ + # We get the model from the versioned app registry; + # if we directly import it, it will be the wrong version. + State = apps.get_model("motions", "State") + for state in State.objects.all(): + if state.required_permission_to_see: + state.access_level = 1 + state.save(skip_autoupdate=True) + + +class Migration(migrations.Migration): + + dependencies = [("motions", "0020_auto_20190119_1425")] + + operations = [ + migrations.AddField( + model_name="state", + name="access_level", + field=models.IntegerField( + choices=[ + (0, "All users with permission to see motions"), + ( + 1, + "Submitters, managers and users with permission to manage metadata", + ), + (2, "Only managers and users with permission to manage metadata"), + (3, "Only managers"), + ], + default=0, + ), + ), + migrations.RunPython(transform_required_permission_to_see_field), + migrations.RemoveField(model_name="state", name="required_permission_to_see"), + ] diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 35a3bacb3..1cc1402f9 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -1054,6 +1054,24 @@ class State(RESTModelMixin, models.Model): state. """ + ALL = 0 + EXTENDED_MANAGERS_AND_SUBMITTER = 1 + EXTENDED_MANAGERS = 2 + MANAGERS_ONLY = 3 + + ACCESS_LEVELS = ( + (ALL, "All users with permission to see motions"), + ( + EXTENDED_MANAGERS_AND_SUBMITTER, + "Submitters, managers and users with permission to manage metadata", + ), + ( + EXTENDED_MANAGERS, + "Only managers and users with permission to manage metadata", + ), + (MANAGERS_ONLY, "Only managers"), + ) + name = models.CharField(max_length=255) """A string representing the state.""" @@ -1075,14 +1093,10 @@ class State(RESTModelMixin, models.Model): Default value is 'primary' (blue). """ - required_permission_to_see = models.CharField(max_length=255, blank=True) + access_level = models.IntegerField(choices=ACCESS_LEVELS, default=0) """ - A permission string. If not empty, the user has to have this permission to - see a motion in this state. - - To use this feature change the database entry of a state object and add - your favourite permission string. You can do this e. g. by editing the - definitions in create_builtin_workflows() in openslides/motions/signals.py. + Defines which users may see motions in this state e. g. only managers, + users with permission to manage metadata and submitters. """ allow_support = models.BooleanField(default=False) diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index 55336f903..008a26bd6 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -102,7 +102,7 @@ class StateSerializer(ModelSerializer): "name", "recommendation_label", "css_class", - "required_permission_to_see", + "access_level", "allow_support", "allow_create_poll", "allow_submitter_edit", @@ -389,7 +389,7 @@ class MotionSerializer(ModelSerializer): polls = MotionPollSerializer(many=True, read_only=True) modified_final_version = CharField(allow_blank=True, required=False) reason = CharField(allow_blank=True, required=False) - state_required_permission_to_see = SerializerMethodField() + state_access_level = SerializerMethodField() text = CharField(allow_blank=True) title = CharField(max_length=255) amendment_paragraphs = AmendmentParagraphsJSONSerializerField(required=False) @@ -424,7 +424,7 @@ class MotionSerializer(ModelSerializer): "supporters", "state", "state_extension", - "state_required_permission_to_see", + "state_access_level", "statute_paragraph", "workflow_id", "recommendation", @@ -531,12 +531,9 @@ class MotionSerializer(ModelSerializer): return result - def get_state_required_permission_to_see(self, motion): + def get_state_access_level(self, motion): """ - Returns the permission (as string) that is required for non - managers that are not submitters to see this motion in this state. - - Hint: Most states have and empty string here so this restriction is - disabled. + Returns the access level of this state. The default is 0 so everybody + with permission to see motions can see this motion. """ - return motion.state.required_permission_to_see + return motion.state.access_level diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index 7e0e93063..f737da93f 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -479,13 +479,11 @@ class RetrieveMotion(TestCase): username=f"user_{index}", password="password" ) - def test_guest_state_with_required_permission_to_see(self): + def test_guest_state_with_access_level(self): config["general_system_enable_anonymous"] = True guest_client = APIClient() state = self.motion.state - state.required_permission_to_see = ( - "permission_that_the_user_does_not_have_leeceiz9hi7iuta4ahY2" - ) + state.access_level = State.MANAGERS_ONLY state.save() # The cache has to be cleared, see: # https://github.com/OpenSlides/OpenSlides/issues/3396 @@ -494,20 +492,17 @@ class RetrieveMotion(TestCase): response = guest_client.get(reverse("motion-detail", args=[self.motion.pk])) self.assertEqual(response.status_code, 404) - def test_admin_state_with_required_permission_to_see(self): + def test_admin_state_with_access_level(self): state = self.motion.state - state.required_permission_to_see = ( - "permission_that_the_user_does_not_have_coo1Iewu8Eing2xahfoo" - ) + state.access_level = State.MANAGERS_ONLY + state.save() response = self.client.get(reverse("motion-detail", args=[self.motion.pk])) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_submitter_state_with_required_permission_to_see(self): + def test_submitter_state_with_access_level(self): state = self.motion.state - state.required_permission_to_see = ( - "permission_that_the_user_does_not_have_eiW8af9caizoh1thaece" - ) + state.access_level = State.EXTENDED_MANAGERS_AND_SUBMITTER state.save() user = get_user_model().objects.create_user( username="username_ohS2opheikaSa5theijo", diff --git a/tests/unit/motions/test_projector.py b/tests/unit/motions/test_projector.py index e372748b5..af1fcd1ce 100644 --- a/tests/unit/motions/test_projector.py +++ b/tests/unit/motions/test_projector.py @@ -29,7 +29,7 @@ def all_data(): "supporters_id": [], "state_id": 1, "state_extension": None, - "state_required_permission_to_see": "", + "state_access_level": 0, "statute_paragraph_id": None, "workflow_id": 1, "recommendation_id": None, @@ -86,7 +86,7 @@ def all_data(): "name": "submitted", "recommendation_label": None, "css_class": "primary", - "required_permission_to_see": "", + "access_level": 0, "allow_support": True, "allow_create_poll": True, "allow_submitter_edit": True, @@ -102,7 +102,7 @@ def all_data(): "name": "accepted", "recommendation_label": "Acceptance", "css_class": "success", - "required_permission_to_see": "", + "access_level": 0, "allow_support": False, "allow_create_poll": False, "allow_submitter_edit": False, @@ -118,7 +118,7 @@ def all_data(): "name": "rejected", "recommendation_label": "Rejection", "css_class": "danger", - "required_permission_to_see": "", + "access_level": 0, "allow_support": False, "allow_create_poll": False, "allow_submitter_edit": False, @@ -134,7 +134,7 @@ def all_data(): "name": "not decided", "recommendation_label": "No decision", "css_class": "default", - "required_permission_to_see": "", + "access_level": 0, "allow_support": False, "allow_create_poll": False, "allow_submitter_edit": False,